/*
 Navicat Premium Data Transfer

 Source Server         : tencent_mysql
 Source Server Type    : MySQL
 Source Server Version : 50646
 Source Host           : 129.211.9.196:3316
 Source Schema         : blog

 Target Server Type    : MySQL
 Target Server Version : 50646
 File Encoding         : 65001

 Date: 31/10/2021 16:40:07
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for album
-- ----------------------------
DROP TABLE IF EXISTS `album`;
CREATE TABLE `album` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '相册名字',
  `album_desc` varchar(500) COLLATE utf8_bin DEFAULT NULL COMMENT '相册描述',
  `cover_img` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '封面图片得url',
  `create_time` datetime DEFAULT NULL,
  `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Table structure for blog
-- ----------------------------
DROP TABLE IF EXISTS `blog`;
CREATE TABLE `blog` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '博客id',
  `category_id` int(11) DEFAULT '1' COMMENT '分类的id',
  `title` varchar(100) DEFAULT NULL COMMENT '标题',
  `summary` varchar(1000) DEFAULT NULL COMMENT '博客概述',
  `img_url` varchar(255) DEFAULT NULL COMMENT '图片',
  `content` mediumtext COMMENT '正文',
  `html_content` mediumtext,
  `blog_catalog` varchar(4096) DEFAULT '[]' COMMENT '目录，对象集合的json格式',
  `author` varchar(45) DEFAULT 'Jann Lee' COMMENT '作者',
  `code` int(11) DEFAULT '0' COMMENT '状态码，如推荐，置顶，转载',
  `view_count` int(11) DEFAULT '1' COMMENT '浏览次数',
  `like_count` int(11) DEFAULT '1' COMMENT '点赞次数',
  `comment_count` int(11) DEFAULT '0' COMMENT '评论次数',
  `create_time` bigint(20) DEFAULT NULL COMMENT '创建时间',
  `update_time` bigint(20) DEFAULT NULL COMMENT '更新时间',
  `blog_status` tinyint(4) DEFAULT '0' COMMENT '状态 0-默认 1-删除  2-草稿',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `blogId` (`id`) USING BTREE,
  KEY `categoryId_idx` (`category_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

-- ----------------------------
-- Records of blog
-- ----------------------------
BEGIN;
INSERT INTO `blog` VALUES (1, 16, '123', '123', '312', '123', NULL, '[]', NULL, 0, 5, 9, NULL, 1562410388000, 1572453367964, 0);
INSERT INTO `blog` VALUES (2, 16, '测试', '123', '12312', '12321', NULL, '[]', NULL, 0, 12, 22, NULL, 1562511170988, 1572453366047, 0);
INSERT INTO `blog` VALUES (3, 16, '123', '1231', '321312', '12321', NULL, '[]', NULL, 0, 3, 1, NULL, 1562511170988, 1572453363998, 0);
INSERT INTO `blog` VALUES (4, 16, '12312', '1231', '321', '213', NULL, '[]', NULL, 0, 10, 2, NULL, 1562511402083, 1572453361634, 0);
INSERT INTO `blog` VALUES (5, 16, '12312', '1231', '12321', '12312', NULL, '[]', NULL, 0, 20, 2, NULL, 1562511474306, 1572453358574, 0);
INSERT INTO `blog` VALUES (6, 16, '12312', '幅度萨芬', NULL, '发::: hljs-left\n士大夫撒旦\n居左\n\n:::\n', NULL, '[]', NULL, 1, 13, 5, NULL, 1562511620133, 1572453370761, 0);
INSERT INTO `blog` VALUES (7, 16, '12312', '幅度萨芬', NULL, '发::: hljs-left\n士大夫撒旦\n居左\n\n:::\n', '<p>发::: hljs-left<br />\n士大夫撒旦<br />\n居左</p>\n<p>:::</p>\n', '[]', NULL, 1, 14, 1, NULL, 1562511679096, 1572453372657, 0);
INSERT INTO `blog` VALUES (8, 16, '123213', 'f\'sa\'d\'f', NULL, ' 撒打发', NULL, '[]', NULL, 0, 1, 11, NULL, 1562600484688, 1563847360108, 0);
INSERT INTO `blog` VALUES (9, 6, '测试blog', '哈哈哈', NULL, '# 一级标题\n\n|column1|column2|column3|\n|-|-|-|\n|content1|content2|content3|\n', '<h1><a id=\"_0\"></a>一级标题</h1>\n<table>\n<thead>\n<tr>\n<th>column1</th>\n<th>column2</th>\n<th>column3</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n</tr>\n</tbody>\n</table>\n', '[]', NULL, 0, 21, 3, NULL, 1562600686077, 1572453374937, 0);
INSERT INTO `blog` VALUES (10, 16, '测试blog', '哈哈哈', NULL, '# 一级标题', NULL, '[]', NULL, 0, 38, 39, NULL, 1562600748491, 1572453377264, 0);
INSERT INTO `blog` VALUES (11, 6, '123123', '士大夫撒旦是的', NULL, '防守打法是  f ', '<p>防守打法是  f</p>\n', '[]', NULL, 1, 1, 1, NULL, 1562600753705, 1563851653022, -1);
INSERT INTO `blog` VALUES (12, 16, '李強123123', '12321', NULL, '12312312', NULL, '[]', NULL, 1, 2, 2, NULL, 1562602197988, 1563845595582, 0);
INSERT INTO `blog` VALUES (13, 16, '新增boke', 'sdf ', NULL, 'f s ', NULL, '[]', NULL, 0, 1, 1, NULL, 1562602331225, 1563845590694, 0);
INSERT INTO `blog` VALUES (14, 6, '李强测试博客', '测试摘要', NULL, '# hello world\n以及标题测试\n## hello world\n在软件系统中，经常有一些特殊的类，必须保证他们在系统中只存在一个实例，才能保证他们的逻辑正确性，以及良好的效率\n\n在软件系统中，经常有一些特殊的类，必须保证他们在系统中只存在一个实例，才能保证他们的逻辑正确性，以及良好的效率\n\n在软件系统中，经常有一些特殊的类，必须保证他们在系统中只存在一个实例，才能保证他们的逻辑正确性，以及良好的效率\n二级标题测试\n### hello world 三级标题\n### hello world 三级标题\n#### hello world 三级标题\n#### hello world 三级标题\n##### hello world 三级标题\n##### hello world 三级标题\n\n```javascript\nsubmitForm() {\n        alert(JSON.stringify(this.blog))\n        this.blog.code = this.blog.code === true ? 0 : 1\n        if (this.id === null || this.id === undefined) {\n          axios.post(\'api/manage/blogs\', this.blog).then(result => {\n            if (result.data.status === 1000) {\n              this.$message.success(\'提交成功！\')\n            } else {\n              this.$message.error(JSON.stringify(result.data.msg))\n            }\n          })\n        } else {\n          axios.put(\'api/manage/blogs/\' + this.id, this.blog).then(result => {\n            if (result.data.status === 1000) {\n              this.$message.success(\'提交成功！\')\n            } else {\n              this.$message.error(JSON.stringify(result.data.msg))\n            }\n          })\n        }\n      },\n\n```\n1231', '<h1><a id=\"hello_world_0\"></a>hello world</h1>\n<p>以及标题测试</p>\n<h2><a id=\"hello_world_2\"></a>hello world</h2>\n<p>在软件系统中，经常有一些特殊的类，必须保证他们在系统中只存在一个实例，才能保证他们的逻辑正确性，以及良好的效率</p>\n<p>在软件系统中，经常有一些特殊的类，必须保证他们在系统中只存在一个实例，才能保证他们的逻辑正确性，以及良好的效率</p>\n<p>在软件系统中，经常有一些特殊的类，必须保证他们在系统中只存在一个实例，才能保证他们的逻辑正确性，以及良好的效率<br />\n二级标题测试</p>\n<h3><a id=\"hello_world__9\"></a>hello world 三级标题</h3>\n<h3><a id=\"hello_world__10\"></a>hello world 三级标题</h3>\n<h4><a id=\"hello_world__11\"></a>hello world 三级标题</h4>\n<h4><a id=\"hello_world__12\"></a>hello world 三级标题</h4>\n<h5><a id=\"hello_world__13\"></a>hello world 三级标题</h5>\n<h5><a id=\"hello_world__14\"></a>hello world 三级标题</h5>\n<pre><div class=\"hljs\"><code class=\"lang-javascript\">submitForm() {\n        alert(<span class=\"hljs-built_in\">JSON</span>.stringify(<span class=\"hljs-keyword\">this</span>.blog))\n        <span class=\"hljs-keyword\">this</span>.blog.code = <span class=\"hljs-keyword\">this</span>.blog.code === <span class=\"hljs-literal\">true</span> ? <span class=\"hljs-number\">0</span> : <span class=\"hljs-number\">1</span>\n        <span class=\"hljs-keyword\">if</span> (<span class=\"hljs-keyword\">this</span>.id === <span class=\"hljs-literal\">null</span> || <span class=\"hljs-keyword\">this</span>.id === <span class=\"hljs-literal\">undefined</span>) {\n          axios.post(<span class=\"hljs-string\">\'api/manage/blogs\'</span>, <span class=\"hljs-keyword\">this</span>.blog).then(<span class=\"hljs-function\"><span class=\"hljs-params\">result</span> =&gt;</span> {\n            <span class=\"hljs-keyword\">if</span> (result.data.status === <span class=\"hljs-number\">1000</span>) {\n              <span class=\"hljs-keyword\">this</span>.$message.success(<span class=\"hljs-string\">\'提交成功！\'</span>)\n            } <span class=\"hljs-keyword\">else</span> {\n              <span class=\"hljs-keyword\">this</span>.$message.error(<span class=\"hljs-built_in\">JSON</span>.stringify(result.data.msg))\n            }\n          })\n        } <span class=\"hljs-keyword\">else</span> {\n          axios.put(<span class=\"hljs-string\">\'api/manage/blogs/\'</span> + <span class=\"hljs-keyword\">this</span>.id, <span class=\"hljs-keyword\">this</span>.blog).then(<span class=\"hljs-function\"><span class=\"hljs-params\">result</span> =&gt;</span> {\n            <span class=\"hljs-keyword\">if</span> (result.data.status === <span class=\"hljs-number\">1000</span>) {\n              <span class=\"hljs-keyword\">this</span>.$message.success(<span class=\"hljs-string\">\'提交成功！\'</span>)\n            } <span class=\"hljs-keyword\">else</span> {\n              <span class=\"hljs-keyword\">this</span>.$message.error(<span class=\"hljs-built_in\">JSON</span>.stringify(result.data.msg))\n            }\n          })\n        }\n      },\n\n</code></div></pre>\n<p>1231</p>\n', '[{\"lev\":1,\"text\":\"hello world\",\"id\":\"hello_world_0\"},{\"lev\":2,\"text\":\"hello world\",\"id\":\"hello_world_2\"},{\"lev\":3,\"text\":\"hello world 三级标题\",\"id\":\"hello_world__9\"},{\"lev\":3,\"text\":\"hello world 三级标题\",\"id\":\"hello_world__10\"},{\"lev\":4,\"text\":\"hello world 三级标题\",\"id\":\"hello_world__11\"},{\"lev\":4,\"text\":\"hello world 三级标题\",\"id\":\"hello_world__12\"},{\"lev\":5,\"text\":\"hello world 三级标题\",\"id\":\"hello_world__13\"},{\"lev\":5,\"text\":\"hello world 三级标题\",\"id\":\"hello_world__14\"}]', NULL, 1, 87, 113, NULL, 1562682490731, 1572453379213, 0);
INSERT INTO `blog` VALUES (15, 16, '测试blog', '测试摘要', NULL, '# hello world\n\n```javascript\nsubmitForm() {\n        alert(JSON.stringify(this.blog))\n        this.blog.code = this.blog.code === true ? 0 : 1\n        if (this.id === null || this.id === undefined) {\n          axios.post(\'api/manage/blogs\', this.blog).then(result => {\n            if (result.data.status === 1000) {\n              this.$message.success(\'提交成功！\')\n            } else {\n              this.$message.error(JSON.stringify(result.data.msg))\n            }\n          })\n        } else {\n          axios.put(\'api/manage/blogs/\' + this.id, this.blog).then(result => {\n            if (result.data.status === 1000) {\n              this.$message.success(\'提交成功！\')\n            } else {\n              this.$message.error(JSON.stringify(result.data.msg))\n            }\n          })\n        }\n      },ffff\n\n```\n> hello\n\n* 你是哪位\n`java` 12312\n\n> fffff\n==标记==\n\n', '<h1><a id=\"hello_world_0\"></a>hello world</h1>\n<pre><code class=\"lang-javascript\">submitForm() {\n        alert(JSON.stringify(this.blog))\n        this.blog.code = this.blog.code === true ? 0 : 1\n        if (this.id === null || this.id === undefined) {\n          axios.post(\'api/manage/blogs\', this.blog).then(result =&gt; {\n            if (result.data.status === 1000) {\n              this.$message.success(\'提交成功！\')\n            } else {\n              this.$message.error(JSON.stringify(result.data.msg))\n            }\n          })\n        } else {\n          axios.put(\'api/manage/blogs/\' + this.id, this.blog).then(result =&gt; {\n            if (result.data.status === 1000) {\n              this.$message.success(\'提交成功！\')\n            } else {\n              this.$message.error(JSON.stringify(result.data.msg))\n            }\n          })\n        }\n      },ffff\n\n</code></pre>\n<blockquote>\n<p>hello</p>\n</blockquote>\n<ul>\n<li>你是哪位<br />\n<code>java</code> 12312</li>\n</ul>\n<blockquote>\n<p>fffff<br />\n<mark>标记</mark></p>\n</blockquote>\n', '[]', NULL, 1, 3, 100, NULL, 1562682497595, 1563853491265, -1);
INSERT INTO `blog` VALUES (16, 6, '设计模式学习笔记-模板方法', '《Head First设计模式》已经读了不止一遍，但是始终没有进行系统的进行总结。所以近期开始总结设计模式相关的知识，从模板方法模式开始，因为是一个我认为是最简单的设计模式。（推荐视频资源[23个设计模式](https://www.bilibili.com/video/av24176315)）', '201908180208_928.jpg?imageView1/JannLee/md/01', '### 提出&解决问题\n\n> 提出问题\n\n **实现制作咖啡功能**。且制作咖啡需要四个步骤 ：\n\n1. 烧水\n2. 冲泡咖啡\n3. 倒入杯中\n4. 加糖\n\n> 代码实现\n\n```java\n/**\n * 一杯加糖咖啡\n *\n * @author Jann Lee\n * @date 2019-07-14 18:37\n */\npublic class Coffee {\n\n    /**\n     * 制作一杯加糖咖啡\n     */\n    public void prepareRecipe() {\n        boilWater();\n        steepTeaBag();\n        portInCup();\n        addLemon();\n    }\n\n    /**\n     * step1: 烧水\n     */\n    private void boilWater() {\n        System.out.println(\"烧水...\");\n    }\n\n    /**\n     * step2：冲泡咖啡\n     */\n    private void steepTeaBag() {\n        System.out.println(\"冲泡咖啡...\");\n    }\n\n    /**\n     * step3: 倒入杯中\n     */\n    private void portInCup() {\n        System.out.println(\"倒入杯中...\");\n    }\n\n    /**\n     * step4: 加糖\n     */\n    private void addLemon() {\n        System.out.println(\"加糖...\");\n    }\n```\n\n> 再次提出问题此时此刻我需要一杯柠檬茶呢？【烧水，冲泡茶包，倒入杯中，加柠檬】\n\n这个问题当然很简单，我们只需要如法炮制即可。\n\n```java\npublic class Tea {\n\n    /**\n     * 制作一杯柠檬茶\n     */\n    public void prepareRecipe(){\n        boilWater();\n        brewCoffeeGrinds();\n        portInCup();\n        addSugarAndMilk();\n    }\n\n    /**\n     * step1: 烧水\n     */\n    private void boilWater() {\n        System.out.println(\"烧水...\");\n    }\n\n    /**\n     * step2：冲泡咖啡\n     */\n    private void brewCoffeeGrinds() {\n        System.out.println(\"冲泡茶包...\");\n    }\n\n    /**\n     * step3: 倒入杯中\n     */\n    private void portInCup() {\n        System.out.println(\"倒入杯中...\");\n    }\n\n    /**\n     * step4: 加柠檬\n     */\n    private void addSugarAndMilk() {\n        System.out.println(\"加入柠檬片...\");\n    }\n}\n```\n\n>  思考\n\n​	如果此时我们又需要一杯不加柠檬的茶，加奶的咖啡...，当然我们可以按照上面方式重新依次实现即可。但是如果你是一个有经验的程序员，或者你学习过设计模式。你可能会发现以上功能实现的**步骤/流程固定**，当需求发生变化时，只有小部分步骤有所**改变**。\n\n### 优化代码\n\n根据面向对象程序的特点，既抽象，封装，继承，多态。我们可以对代码进行抽象，将公共代码提取到基类。我们将咖啡和茶抽象成咖啡因饮料，将其中相同的两步，烧水和倒入杯中再父类中实现，将冲泡和添加调料延迟到子类。\n\n1. 定义一个基类\n\n```java\npublic abstract class CafeineBeverage {\n    /**\n     * 制作一杯咖啡因饮料\n     */\n    public void prepareRecipe() {\n        boilWater();\n        brew();\n        portInCup();\n        addCondiments();\n    }\n\n    /**\n     * step1: 烧水\n     */\n    private void boilWater() {\n        System.out.println(\"烧水...\");\n    }\n\n    /**\n     * step2：冲泡\n     */\n    protected abstract void brew();\n\n    /**\n     * step3: 入杯中\n     */\n    private void portInCup() {\n        System.out.println(\"倒入杯中...\");\n    }\n\n    /**\n     * step4: 加调料\n     */\n    protected abstract void addCondiments();\n}\n```\n\n```java\n// 一杯加糖咖啡\npublic class CoffeeBeverage extends CafeineBeverage{\n\n    @Override\n    protected void brew() {\n        System.out.println(\"冲泡咖啡...\");\n    }\n\n    @Override\n    protected void addCondiments() {\n        System.out.println(\"加糖...\");\n    }\n}\n```\n\n```java\n// 一杯柠檬茶\npublic class TeaBeverage extends CafeineBeverage {\n    @Override\n    protected void brew() {\n        System.out.println(\"冲泡茶包...\");\n    }\n\n    @Override\n    protected void addCondiments() {\n        System.out.println(\"加柠檬...\");\n    }\n}\n```\n\n## 模板方法模式\n\n如果按以上方式对代码进行了优化，其实就实现了**模板方法**模式。一下是模板方法模式相关概念。\n\n> 动机\n\n- 在软件构建过程中，对于某一项任务，它常常有**稳定**的整体操作结构，但是各个子步骤却有很多**改变**的需求，或者由于固有的原因（比如框架与应用之间的关系）而无法和任务的整体结构同时实现\n- 如何在确定**稳定**的操作结构的前提下，来灵活应对各个子步骤的变化或者晚期实现需求？\n\n> 定义\n\n 定义一个操作中算法的骨架（稳定），而将一些步骤延迟（变化）到子类。Template Method使得子类可以不改变（复用）一个算法的结构，即可重新定义（override）该算法的特定步骤。\n\n> 要点总结\n\n- Template Method是一种非常基础性的设计模式，在面向对象系统中，有着大量的应用。他用最简洁的机制(抽象类的多态，为很多应用框架提供了灵活的扩展点，是代码复用方面最基本实现结构)\n- 除了可以灵活应对子步骤的变化外，“**不要调用我，让我来调用你**”的反向控制结构是Template Method的典型应用\n- 在具体实现方面，被Template Method调用得虚方法可以有实现，也可以没有实现(抽象方法)，但一般推荐设置为protected方法\n\n> 类图：\n\n![](http://source.mycookies.cn/201907142249_120.jpg?imageView1/JannLee/md/01)\n\n ', '<h3><a id=\"_0\"></a>提出&amp;解决问题</h3>\n<blockquote>\n<p>提出问题</p>\n</blockquote>\n<p><strong>实现制作咖啡功能</strong>。且制作咖啡需要四个步骤 ：</p>\n<ol>\n<li>烧水</li>\n<li>冲泡咖啡</li>\n<li>倒入杯中</li>\n<li>加糖</li>\n</ol>\n<blockquote>\n<p>代码实现</p>\n</blockquote>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 一杯加糖咖啡\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-14 18:37\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Coffee</span> </span>{\n\n    <span class=\"hljs-comment\">/**\n     * 制作一杯加糖咖啡\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">prepareRecipe</span><span class=\"hljs-params\">()</span> </span>{\n        boilWater();\n        steepTeaBag();\n        portInCup();\n        addLemon();\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step1: 烧水\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">boilWater</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"烧水...\"</span>);\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step2：冲泡咖啡\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">steepTeaBag</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"冲泡咖啡...\"</span>);\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step3: 倒入杯中\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">portInCup</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"倒入杯中...\"</span>);\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step4: 加糖\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">addLemon</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"加糖...\"</span>);\n    }\n</code></div></pre>\n<blockquote>\n<p>再次提出问题此时此刻我需要一杯柠檬茶呢？【烧水，冲泡茶包，倒入杯中，加柠檬】</p>\n</blockquote>\n<p>这个问题当然很简单，我们只需要如法炮制即可。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Tea</span> </span>{\n\n    <span class=\"hljs-comment\">/**\n     * 制作一杯柠檬茶\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">prepareRecipe</span><span class=\"hljs-params\">()</span></span>{\n        boilWater();\n        brewCoffeeGrinds();\n        portInCup();\n        addSugarAndMilk();\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step1: 烧水\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">boilWater</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"烧水...\"</span>);\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step2：冲泡咖啡\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">brewCoffeeGrinds</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"冲泡茶包...\"</span>);\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step3: 倒入杯中\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">portInCup</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"倒入杯中...\"</span>);\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step4: 加柠檬\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">addSugarAndMilk</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"加入柠檬片...\"</span>);\n    }\n}\n</code></div></pre>\n<blockquote>\n<p>思考</p>\n</blockquote>\n<p>​	如果此时我们又需要一杯不加柠檬的茶，加奶的咖啡…，当然我们可以按照上面方式重新依次实现即可。但是如果你是一个有经验的程序员，或者你学习过设计模式。你可能会发现以上功能实现的<strong>步骤/流程固定</strong>，当需求发生变化时，只有小部分步骤有所<strong>改变</strong>。</p>\n<h3><a id=\"_112\"></a>优化代码</h3>\n<p>根据面向对象程序的特点，既抽象，封装，继承，多态。我们可以对代码进行抽象，将公共代码提取到基类。我们将咖啡和茶抽象成咖啡因饮料，将其中相同的两步，烧水和倒入杯中再父类中实现，将冲泡和添加调料延迟到子类。</p>\n<ol>\n<li>定义一个基类</li>\n</ol>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">abstract</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">CafeineBeverage</span> </span>{\n    <span class=\"hljs-comment\">/**\n     * 制作一杯咖啡因饮料\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">prepareRecipe</span><span class=\"hljs-params\">()</span> </span>{\n        boilWater();\n        brew();\n        portInCup();\n        addCondiments();\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step1: 烧水\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">boilWater</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"烧水...\"</span>);\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step2：冲泡\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">protected</span> <span class=\"hljs-keyword\">abstract</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">brew</span><span class=\"hljs-params\">()</span></span>;\n\n    <span class=\"hljs-comment\">/**\n     * step3: 入杯中\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">portInCup</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"倒入杯中...\"</span>);\n    }\n\n    <span class=\"hljs-comment\">/**\n     * step4: 加调料\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">protected</span> <span class=\"hljs-keyword\">abstract</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">addCondiments</span><span class=\"hljs-params\">()</span></span>;\n}\n</code></div></pre>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">// 一杯加糖咖啡</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">CoffeeBeverage</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">CafeineBeverage</span></span>{\n\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">protected</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">brew</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"冲泡咖啡...\"</span>);\n    }\n\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">protected</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">addCondiments</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"加糖...\"</span>);\n    }\n}\n</code></div></pre>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">// 一杯柠檬茶</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">TeaBeverage</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">CafeineBeverage</span> </span>{\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">protected</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">brew</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"冲泡茶包...\"</span>);\n    }\n\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">protected</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">addCondiments</span><span class=\"hljs-params\">()</span> </span>{\n        System.out.println(<span class=\"hljs-string\">\"加柠檬...\"</span>);\n    }\n}\n</code></div></pre>\n<h2><a id=\"_187\"></a>模板方法模式</h2>\n<p>如果按以上方式对代码进行了优化，其实就实现了<strong>模板方法</strong>模式。一下是模板方法模式相关概念。</p>\n<blockquote>\n<p>动机</p>\n</blockquote>\n<ul>\n<li>在软件构建过程中，对于某一项任务，它常常有<strong>稳定</strong>的整体操作结构，但是各个子步骤却有很多<strong>改变</strong>的需求，或者由于固有的原因（比如框架与应用之间的关系）而无法和任务的整体结构同时实现</li>\n<li>如何在确定<strong>稳定</strong>的操作结构的前提下，来灵活应对各个子步骤的变化或者晚期实现需求？</li>\n</ul>\n<blockquote>\n<p>定义</p>\n</blockquote>\n<p>定义一个操作中算法的骨架（稳定），而将一些步骤延迟（变化）到子类。Template Method使得子类可以不改变（复用）一个算法的结构，即可重新定义（override）该算法的特定步骤。</p>\n<blockquote>\n<p>要点总结</p>\n</blockquote>\n<ul>\n<li>Template Method是一种非常基础性的设计模式，在面向对象系统中，有着大量的应用。他用最简洁的机制(抽象类的多态，为很多应用框架提供了灵活的扩展点，是代码复用方面最基本实现结构)</li>\n<li>除了可以灵活应对子步骤的变化外，“<strong>不要调用我，让我来调用你</strong>”的反向控制结构是Template Method的典型应用</li>\n<li>在具体实现方面，被Template Method调用得虚方法可以有实现，也可以没有实现(抽象方法)，但一般推荐设置为protected方法</li>\n</ul>\n<blockquote>\n<p>类图：</p>\n</blockquote>\n<p><img src=\"http://source.mycookies.cn/201907142249_120.jpg?imageView1/JannLee/md/01\" alt=\"\" /></p>\n', '[{\"lev\":3,\"text\":\"提出&解决问题\",\"id\":\"_0\"},{\"lev\":3,\"text\":\"优化代码\",\"id\":\"_112\"},{\"lev\":2,\"text\":\"模板方法模式\",\"id\":\"_187\"}]', NULL, 0, 326, 31739, NULL, 1563117582981, 1571762617499, 1);
INSERT INTO `blog` VALUES (17, 6, '设计模式学习笔记-单例模式', '在系统开发设计中，总会存在这么几种情况，①需要频繁创建销毁的对象，②创建对象需要消耗很多资源，但又经常用到的对象（如工具类对象，频繁访问数据库或文件的对象，数据源，session工厂等)；③某个类只能有一个对象，如应用中的Application类；这时就应该考虑使用单例模式', '201908180205_677.jpg?imageView1/JannLee/md/01', '# 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n# 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n##  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n## 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n## 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n# 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n# 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h1><a id=\"_0\"></a>一.单例模式的动机</h1>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h1><a id=\"_5\"></a>二.模式定义</h1>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h2><a id=\"_9\"></a>饿汉式</h2>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h2><a id=\"_67\"></a>懒汉式</h2>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h2><a id=\"_194\"></a>静态内部类</h2>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h1><a id=\"_266\"></a>三.要点总结</h1>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h1><a id=\"_274\"></a>四.思考</h1>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[{\"lev\":1,\"text\":\"一.单例模式的动机\",\"id\":\"_0\"},{\"lev\":1,\"text\":\"二.模式定义\",\"id\":\"_5\"},{\"lev\":2,\"text\":\"饿汉式\",\"id\":\"_9\"},{\"lev\":2,\"text\":\"懒汉式\",\"id\":\"_67\"},{\"lev\":2,\"text\":\"静态内部类\",\"id\":\"_194\"},{\"lev\":3,\"text\":\"只包含单个元素的枚举类型\",\"id\":\"_225\"},{\"lev\":1,\"text\":\"三.要点总结\",\"id\":\"_266\"},{\"lev\":1,\"text\":\"四.思考\",\"id\":\"_274\"}]', NULL, 0, 551, 94, NULL, 1563705620697, 1571762620419, 1);
INSERT INTO `blog` VALUES (18, 16, '测试博客', '测试', NULL, '	123', '<pre><code>123</code></pre>\n', '[]', NULL, 0, 1, 1, NULL, 1563780800615, 1563786688503, 0);
INSERT INTO `blog` VALUES (19, 16, '测试博客', '测试', NULL, '	123', '<pre><code>123</code></pre>\n', '[]', NULL, 1, 1, 1, NULL, 1563780808501, 1563786671592, 0);
INSERT INTO `blog` VALUES (20, 16, '测试blog', '的', NULL, '地方', '<p>地方</p>\n', '[]', NULL, 0, 1, 1, NULL, 1563781229723, 1563786667011, 0);
INSERT INTO `blog` VALUES (21, 16, '测试blog', '的', NULL, '地方', '<p>地方</p>\n', '[]', NULL, 1, 1, 1, NULL, 1563781335653, 1563786664165, 0);
INSERT INTO `blog` VALUES (22, 16, '测试blog', '的', NULL, '地方### 三级标题', '<p>地方### 三级标题</p>\n', '[]', NULL, 0, 1, 1, NULL, 1563781659829, 1563786661665, 0);
INSERT INTO `blog` VALUES (23, 16, '测试blog', '的', NULL, '地方### 三级标题', '<p>地方### 三级标题</p>\n', '[]', NULL, 1, 1, 1, NULL, 1563781676698, 1563786625164, 0);
INSERT INTO `blog` VALUES (24, 16, '测试blog', '的', NULL, '地方### 三级标题', '<p>地方### 三级标题</p>\n', '[]', NULL, 1, 1, 1, NULL, 1563781713813, 1563786617478, 0);
INSERT INTO `blog` VALUES (25, 6, '测试123', '123131', 'hello.jpb', '123123', '<p>123123</p>\n', '[]', NULL, 1, 1, 1, NULL, 1563847068362, 1563847085540, 0);
INSERT INTO `blog` VALUES (26, 6, '测试123', '123131', 'hello.jpb11', '123123', '<p>123123</p>\n', '[]', NULL, 2, 9, 24, NULL, 1563847068366, 1564394474273, 0);
INSERT INTO `blog` VALUES (27, 16, '测试', '发多少', '', '水电费', '<p>水电费</p>\n', '[]', 'Jann Lee', 1, 1, 1, 0, 1563853553134, 1563853553179, -1);
INSERT INTO `blog` VALUES (28, 16, '测试', '发多少', '', '水电费', '<p>水电费</p>\n', '[]', 'Jann Lee', 1, 1, 1, 0, 1563853559442, 1563853559478, -1);
INSERT INTO `blog` VALUES (29, 16, '测试', '发多少', '', '水电费', '<p>水电费</p>\n', '[]', 'Jann Lee', 1, 1, 1, 0, 1563853565138, 1563853565194, -1);
INSERT INTO `blog` VALUES (30, 16, '测试', '发多少', '', '水电费', '<p>水电费</p>\n', '[]', 'Jann Lee', 0, 1, 1, 0, 1563853571438, 1563853571469, -1);
INSERT INTO `blog` VALUES (31, 6, '测试123', '12321312', '', '姓名| 年龄| 备注|\n|-|-|-|\n|content1|content2|content3|\n', '<table>\n<thead>\n<tr>\n<th>姓名</th>\n<th>年龄</th>\n<th>备注</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n</tr>\n</tbody>\n</table>\n', '[]', 'Jann Lee', 0, 1, 1, 0, 1566059441474, 1566059441483, -1);
INSERT INTO `blog` VALUES (32, 6, '测试123', '12321312', '', '姓名| 年龄| 备注|姓名| 年龄| 备注|\n|-|-|-|-|-|-|\n|李强|123|的撒旦|李强|123|的撒旦|\n|李强|123|的撒旦|李强|123|的撒旦|\n|李强|123|的撒旦|李强|123|的撒旦|\n\n', '<table>\n<thead>\n<tr>\n<th>姓名</th>\n<th>年龄</th>\n<th>备注</th>\n<th>姓名</th>\n<th>年龄</th>\n<th>备注</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>李强</td>\n<td>123</td>\n<td>的撒旦</td>\n<td>李强</td>\n<td>123</td>\n<td>的撒旦</td>\n</tr>\n<tr>\n<td>李强</td>\n<td>123</td>\n<td>的撒旦</td>\n<td>李强</td>\n<td>123</td>\n<td>的撒旦</td>\n</tr>\n<tr>\n<td>李强</td>\n<td>123</td>\n<td>的撒旦</td>\n<td>李强</td>\n<td>123</td>\n<td>的撒旦</td>\n</tr>\n</tbody>\n</table>\n', '[]', 'Jann Lee', 0, 1, 1, 0, 1566059483942, 1566059483951, -1);
INSERT INTO `blog` VALUES (33, 6, '李强测试博客', '测试博客', '', '|column1|column2|column3|column1|column2|column3|\n|-|-|-|-|-|-|\n|content1|content2|content3|content1|content2|content3|\n|content1|content2|content3|content1|content2|content3|\n|content1|content2|content3|content1|content2|content3|\n|content1|content2|content3|content1|content2|content3|', '<table>\n<thead>\n<tr>\n<th>column1</th>\n<th>column2</th>\n<th>column3</th>\n<th>column1</th>\n<th>column2</th>\n<th>column3</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n</tr>\n<tr>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n</tr>\n<tr>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n</tr>\n<tr>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n<td>content1</td>\n<td>content2</td>\n<td>content3</td>\n</tr>\n</tbody>\n</table>\n', '[]', 'Jann Lee', 0, 1, 1, 0, 1566059566198, 1566059566218, -1);
INSERT INTO `blog` VALUES (34, 16, '123213', '测试博客', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[]', 'Jann Lee', 0, 1, 1, 0, 1566094835008, 1566094835017, -1);
INSERT INTO `blog` VALUES (35, 16, '测试blog', '2131231', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[]', 'Jann Lee', 0, 1, 1, 0, 1566095205943, 1566095206501, -1);
INSERT INTO `blog` VALUES (36, 16, '测试博客', '撒打发', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[]', 'Jann Lee', 0, 1, 1, 0, 1566095260190, 1566095260241, -1);
INSERT INTO `blog` VALUES (37, 16, '测试博客', '撒打发', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[]', 'Jann Lee', 0, 1, 1, 0, 1566095339663, 1566095339709, -1);
INSERT INTO `blog` VALUES (38, 16, '测试博客', '撒打发', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[]', 'Jann Lee', 0, 1, 1, 0, 1566095366725, 1566095366768, -1);
INSERT INTO `blog` VALUES (39, 16, '测试', '发生的f', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[{\"lev\":3,\"text\":\"一.单例模式的动机\",\"id\":\"_0\"},{\"lev\":3,\"text\":\"二.模式定义\",\"id\":\"_5\"},{\"lev\":4,\"text\":\"饿汉式\",\"id\":\"_9\"},{\"lev\":4,\"text\":\"懒汉式\",\"id\":\"_67\"},{\"lev\":3,\"text\":\"静态内部类\",\"id\":\"_194\"},{\"lev\":3,\"text\":\"只包含单个元素的枚举类型\",\"id\":\"_225\"},{\"lev\":3,\"text\":\"三.要点总结\",\"id\":\"_266\"},{\"lev\":3,\"text\":\"四.思考\",\"id\":\"_274\"}]', 'Jann Lee', 0, 1, 1, 0, 1566095386014, 1566095386063, -1);
INSERT INTO `blog` VALUES (40, 16, '测试', '发生的f', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[{\"lev\":3,\"text\":\"一.单例模式的动机\",\"id\":\"_0\"},{\"lev\":3,\"text\":\"二.模式定义\",\"id\":\"_5\"},{\"lev\":4,\"text\":\"饿汉式\",\"id\":\"_9\"},{\"lev\":4,\"text\":\"懒汉式\",\"id\":\"_67\"},{\"lev\":3,\"text\":\"静态内部类\",\"id\":\"_194\"},{\"lev\":3,\"text\":\"只包含单个元素的枚举类型\",\"id\":\"_225\"},{\"lev\":3,\"text\":\"三.要点总结\",\"id\":\"_266\"},{\"lev\":3,\"text\":\"四.思考\",\"id\":\"_274\"}]', 'Jann Lee', 0, 1, 1, 0, 1566095428672, 1566095428763, -1);
INSERT INTO `blog` VALUES (41, 16, '测试', '发生的f', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[{\"lev\":3,\"text\":\"一.单例模式的动机\",\"id\":\"_0\"},{\"lev\":3,\"text\":\"二.模式定义\",\"id\":\"_5\"},{\"lev\":4,\"text\":\"饿汉式\",\"id\":\"_9\"},{\"lev\":4,\"text\":\"懒汉式\",\"id\":\"_67\"},{\"lev\":3,\"text\":\"静态内部类\",\"id\":\"_194\"},{\"lev\":3,\"text\":\"只包含单个元素的枚举类型\",\"id\":\"_225\"},{\"lev\":3,\"text\":\"三.要点总结\",\"id\":\"_266\"},{\"lev\":3,\"text\":\"四.思考\",\"id\":\"_274\"}]', 'Jann Lee', 0, 1, 1, 0, 1566095492620, 1566095493745, -1);
INSERT INTO `blog` VALUES (42, 16, '测试', '发生的f', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', NULL, NULL, 0, 1, 1, NULL, 1566095573978, 1566097289714, 0);
INSERT INTO `blog` VALUES (43, 16, '测试', '发生的f', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[{\"lev\":3,\"text\":\"一.单例模式的动机\",\"id\":\"_0\"},{\"lev\":3,\"text\":\"二.模式定义\",\"id\":\"_5\"},{\"lev\":4,\"text\":\"饿汉式\",\"id\":\"_9\"},{\"lev\":4,\"text\":\"懒汉式\",\"id\":\"_67\"},{\"lev\":3,\"text\":\"静态内部类\",\"id\":\"_194\"},{\"lev\":3,\"text\":\"只包含单个元素的枚举类型\",\"id\":\"_225\"},{\"lev\":3,\"text\":\"三.要点总结\",\"id\":\"_266\"},{\"lev\":3,\"text\":\"四.思考\",\"id\":\"_274\"}]', 'Jann Lee', 0, 1, 1, 0, 1566095572927, 1566095574356, -1);
INSERT INTO `blog` VALUES (44, 16, '测试', '发生的f', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[{\"lev\":3,\"text\":\"一.单例模式的动机\",\"id\":\"_0\"},{\"lev\":3,\"text\":\"二.模式定义\",\"id\":\"_5\"},{\"lev\":4,\"text\":\"饿汉式\",\"id\":\"_9\"},{\"lev\":4,\"text\":\"懒汉式\",\"id\":\"_67\"},{\"lev\":3,\"text\":\"静态内部类\",\"id\":\"_194\"},{\"lev\":3,\"text\":\"只包含单个元素的枚举类型\",\"id\":\"_225\"},{\"lev\":3,\"text\":\"三.要点总结\",\"id\":\"_266\"},{\"lev\":3,\"text\":\"四.思考\",\"id\":\"_274\"}]', 'Jann Lee', 0, 1, 1, 0, 1566095718643, 1566095719081, -1);
INSERT INTO `blog` VALUES (45, 16, '测试', '发生的f', '', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', '[{\"lev\":3,\"text\":\"一.单例模式的动机\",\"id\":\"_0\"},{\"lev\":3,\"text\":\"二.模式定义\",\"id\":\"_5\"},{\"lev\":4,\"text\":\"饿汉式\",\"id\":\"_9\"},{\"lev\":4,\"text\":\"懒汉式\",\"id\":\"_67\"},{\"lev\":3,\"text\":\"静态内部类\",\"id\":\"_194\"},{\"lev\":3,\"text\":\"只包含单个元素的枚举类型\",\"id\":\"_225\"},{\"lev\":3,\"text\":\"三.要点总结\",\"id\":\"_266\"},{\"lev\":3,\"text\":\"四.思考\",\"id\":\"_274\"}]', 'Jann Lee', 0, 1, 1, 0, 1566095718643, 1566095719077, -1);
INSERT INTO `blog` VALUES (46, 16, '测试', '测试博客', '长度的', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', NULL, NULL, 0, 1, 1, NULL, 1566096887611, 1566097291852, 0);
INSERT INTO `blog` VALUES (47, 16, '测试', '测试博客', '长度的', '### 一.单例模式的动机\n\n- 在软件系统中，经常有一些特殊的类，必须保证他们在系统中**只存在一个实例**，才能保证他们的逻辑正确性，以及**良好的效率**\n- 如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任\n\n### 二.模式定义\n\n`确保类只有一个实例，并提供全局访问点。`\n\n####  饿汉式\n\n在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。\n\n```java\n/**\n * 饿汉式[工厂方法]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:28\n */\npublic class Singleton1 {\n\n    private static final Singleton1 instance = new Singleton1();\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        return instance;\n    }\n}\n\n /**\n * 饿汉式[公有域]\n */\npublic class Singleton2 {\n    \n    public static final Singleton2 instance = new Singleton2();\n    \n    private Singleton2() {\n    }\n}\n /**\n * 饿汉式[静态代码块，工厂方法]\n */\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    static {\n        instance = new Singleton3();\n    }\n\n    private Singleton3() {\n    }\n\n    public static Singleton3 getInstance() {\n        return instance;\n    }\n}\n```\n\n以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。\n\n**优点**：类装在时候完成实例化，避免了多线程问题\n**缺点**：可能造成内存浪费(可能实例从来没有被使用到)\n\n#### 懒汉式\n\n顾名思义，因为懒，所以在用到时才会进行实例化。\n\n实现思路：私有化构造方法->  声明成员变量 -> 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】\n\n```java\n/**\n * 1.懒汉式[非线程安全]\n *\n * @author Jann Lee\n * @date 2019-07-21 14:31\n **/\npublic class Singleton1 {\n\n    private static Singleton1 instance;\n\n    private Singleton1() {\n    }\n\n    public static Singleton1 getInstance() {\n        if (instance == null) {\n            instance = new Singleton1();\n        }\n        return instance;\n    }\n}\n```\n\n上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。\n\n![](http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01)\n\n为了解决线程安全问题， 将方法进行同步\n\n```java\n/**\n * 2.懒汉式[同步方法]\n **/\npublic class Singleton2 {\n\n    private static Singleton2 instance;\n\n    private Singleton2() {\n    }\n    \n   /**\n     * 同步方法，保证线程安全\n     */\n    public static synchronized Singleton2 getInstance() {\n        if (instance == null) {\n            instance = new Singleton2();\n        }\n        return instance;\n    }\n｝\n```\n\n的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。\n\n这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。\n\n```java\n/**\n * 4.懒汉式[同步代码块]\n **/\npublic class Singleton3 {\n\n    private static Singleton3 instance;\n\n    private Singleton3() {\n    }\n\n    /**\n     * 同步代码块，并不能保证线程安全\n     */\n    public static Singleton3 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton3.class) {\n                instance = new Singleton3();\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**这是一种错误的实现方式**，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。\n\n解决上述问题，我们只需要在同步代码块中加入校验。\n\n```java\n/**\n * 懒汉式[双重检查锁]\n **/\npublic class Singleton4 {\n\n    private static volatile Singleton4 instance;\n\n    private Singleton4() {\n    }\n\n    public static Singleton4 getInstance() {\n        if (instance == null) {\n            synchronized (Singleton4.class) {\n                if (instance == null) {\n                    instance = new Singleton4();\n                }\n            }\n        }\n        return instance;\n    }\n}\n```\n\n**注意：**此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生**指令重排序**。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。\n\n> 在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。\n>\n> java中创建对象分为 三个步骤【可以简单理解为三条指令】\n>\n> 1. 分配内存，内存空间初始化\n> 2. 对象初始化，类的元数据信息，hashCode等信息\n> 3. 将内存地址返回\n>\n> 如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。\n\n### 静态内部类\n\n在静态内部类中持有一个对象。\n\n```java\n/**\n * 使用静态内部类实现单例模式\n *\n * @author Jann Lee\n * @date 2019-07-21 14:47\n **/\npublic class Singleton {\n\n    private Singleton (){}\n\n    public static Singleton getInstance() {\n        return ClassHolder.singleton;\n    }\n\n    /**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */\n    private static class ClassHolder {\n       private static final Singleton singleton = new Singleton();\n    }\n}\n```\n\n静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。\n\n### 只包含单个元素的枚举类型\n\n在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。\n\n```java\n/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * @author Jann Lee\n * @date 2019-07-21 14:55\n **/\npublic enum Singleton {\n\n    INSTANCE;\n\n    private String name;\n\n    private int age;\n\n    Singleton04() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public int getAge() {\n        return age;\n    }\n\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\n### 三.要点总结\n\n![](http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01)\n\n- SIngleton模式中的实例构造器可以设置为protected以允许字类派生\n- Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。\n- 如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。\n\n### 四.思考\n\n1. java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）\n2. volatile关键字是一个非常重要的关键字，它有那些功能？\n\n', '<h3><a id=\"_0\"></a>一.单例模式的动机</h3>\n<ul>\n<li>在软件系统中，经常有一些特殊的类，必须保证他们在系统中<strong>只存在一个实例</strong>，才能保证他们的逻辑正确性，以及<strong>良好的效率</strong></li>\n<li>如何绕过常规的构造器，提供以中机制保证一个类只有一个实例？这应该是类设计的责任，而不是使用者的责任</li>\n</ul>\n<h3><a id=\"_5\"></a>二.模式定义</h3>\n<p><code>确保类只有一个实例，并提供全局访问点。</code></p>\n<h4><a id=\"_9\"></a>饿汉式</h4>\n<p>在类初始化完成之后就完成了对象创建, 无论是否使用都要提前进行实例化。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 饿汉式[工厂方法]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:28\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton1 instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n <span class=\"hljs-comment\">/**\n * 饿汉式[公有域]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n    \n    <span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton2 instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n    \n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n}\n <span class=\"hljs-comment\">/**\n * 饿汉式[静态代码块，工厂方法]\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-keyword\">static</span> {\n        instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>以上三种写法仅仅是在代码实现上的差异，是“饿汉式”最常见的实现。</p>\n<p><strong>优点</strong>：类装在时候完成实例化，避免了多线程问题<br />\n<strong>缺点</strong>：可能造成内存浪费(可能实例从来没有被使用到)</p>\n<h4><a id=\"_67\"></a>懒汉式</h4>\n<p>顾名思义，因为懒，所以在用到时才会进行实例化。</p>\n<p>实现思路：私有化构造方法-&gt;  声明成员变量 -&gt; 提供公共方法访问【如果成员变量不为空，直接返回，如果为空创建后返回】</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1.懒汉式[非线程安全]\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:31\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton1</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton1 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton1</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton1 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton1();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>上述实现方式是最容易被想到的，但是应该也算是一种错误的实现方式，因为再多线程环境下一个对象可能被创建了多次。</p>\n<p><img src=\"http://source.mycookies.cn/201907211710_309.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<p>为了解决线程安全问题， 将方法进行同步</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 2.懒汉式[同步方法]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton2</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton2 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton2</span><span class=\"hljs-params\">()</span> </span>{\n    }\n    \n   <span class=\"hljs-comment\">/**\n     * 同步方法，保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">synchronized</span> Singleton2 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            instance = <span class=\"hljs-keyword\">new</span> Singleton2();\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n｝\n</code></div></pre>\n<p>的确，这样实现解决了并发情况下带来的问题。但是每次获取对象都需要进行同步，然而对象创建只需要进行一次即可；因为并发读取数据不需要进行同步，所以这种在方法进行同步是没有必要的，在并发情况下肯定会带来性能上的损失。</p>\n<p>这时我们首先想到的时缩小同步范围，只有在创建对象的时候使用同步，即使用同步代码块实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 4.懒汉式[同步代码块]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton3</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Singleton3 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton3</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 同步代码块，并不能保证线程安全\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton3 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton3.class) {\n                instance = <span class=\"hljs-keyword\">new</span> Singleton3();\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>这是一种错误的实现方式</strong>，虽然减少了加锁范围，但是又回到了并发环境下的重复创建对象的问题，具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争，线程1获取到了锁，线程2挂起，执行完毕后释放锁，此时线程二获取到锁后回接着往下执行，再次创建对象。</p>\n<p>解决上述问题，我们只需要在同步代码块中加入校验。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 懒汉式[双重检查锁]\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton4</span> </span>{\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">volatile</span> Singleton4 instance;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton4</span><span class=\"hljs-params\">()</span> </span>{\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton4 <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">synchronized</span> (Singleton4.class) {\n                <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n                    instance = <span class=\"hljs-keyword\">new</span> Singleton4();\n                }\n            }\n        }\n        <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p><strong>注意：<strong>此处除了再次校验是否为空之外，还给成员变量之前加上了一个volatile关键字，这个非常重要。因为在代码执行过程中会发生</strong>指令重排序</strong>。这里你只需要知道加上volatile之后能保证指令不会被重排序，程序能正确执行，而不加则可能不出错。</p>\n<blockquote>\n<p>在编译器和处理器为了提高程序的运行性能，会对指令进行重新排序。即代码会被编译成操作指令，而指令执行顺序可能会发生变化。</p>\n<p>java中创建对象分为 三个步骤【可以简单理解为三条指令】</p>\n<ol>\n<li>分配内存，内存空间初始化</li>\n<li>对象初始化，类的元数据信息，hashCode等信息</li>\n<li>将内存地址返回</li>\n</ol>\n<p>如果2，3顺序发生了变化，另一个线程获得锁时恰好还没有完成对象初始化，即instance指向null，就会重复创建对象。</p>\n</blockquote>\n<h3><a id=\"_194\"></a>静态内部类</h3>\n<p>在静态内部类中持有一个对象。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用静态内部类实现单例模式\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:47\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-title\">Singleton</span> <span class=\"hljs-params\">()</span></span>{}\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> ClassHolder.singleton;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * Singleton装载完成后，不会创建对象\n     * 调用getInstance时候，静态内部类ClassHolder才进行装载\n     */</span>\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ClassHolder</span> </span>{\n       <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">final</span> Singleton singleton = <span class=\"hljs-keyword\">new</span> Singleton();\n    }\n}\n</code></div></pre>\n<p>静态内部类的实现则是根据java语言特性实现的，即让静态内部类持有一个对象；根据类加载机机制的特点，每个类只会加载一次，并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次，无论是否在多线程环境中。</p>\n<h3><a id=\"_225\"></a>只包含单个元素的枚举类型</h3>\n<p>在java中枚举也是类的一种，实际上枚举的每一个元素都是一个枚举类的对象，可以理解为对类的一种封装，默认私有构造方法，且不能使用public修饰。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 枚举实现单例模式\n *\n * 为了便于理解给枚举类添加了两个属性\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-07-21 14:55\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">enum</span> Singleton {\n\n    INSTANCE;\n\n    <span class=\"hljs-keyword\">private</span> String name;\n\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">int</span> age;\n\n    Singleton04() {\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> String <span class=\"hljs-title\">getName</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setName</span><span class=\"hljs-params\">(String name)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.name = name;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">getAge</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">return</span> age;\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">setAge</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> age)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.age = age;\n    }\n}\n</code></div></pre>\n<h3><a id=\"_266\"></a>三.要点总结</h3>\n<p><img src=\"http://source.mycookies.cn/201907211822_665.png?imageView1/JannLee/md/01\" alt=\"\" /></p>\n<ul>\n<li>SIngleton模式中的实例构造器可以设置为protected以允许字类派生</li>\n<li>Singleton模式一般不要支持拷贝构造函数和clone接口，因为这有可能导致多个对象实例，与Singleton模式的初衷违背。</li>\n<li>如何实现多线程环境下安全的Singleton，注意对双重检查锁的正确实现。</li>\n</ul>\n<h3><a id=\"_274\"></a>四.思考</h3>\n<ol>\n<li>java中创建对象的方式有很多种，其中当然包括反射，反序列化，那么上述各种设计模式还能保证对象只会被创建一次吗？（这个问题会在下一篇 中进行分析）</li>\n<li>volatile关键字是一个非常重要的关键字，它有那些功能？</li>\n</ol>\n', NULL, NULL, 0, 1, 1, NULL, 1566096888189, 1566097294296, 0);
INSERT INTO `blog` VALUES (50, 6, 'ce hidfs', 'fdsfasdf', '', '', NULL, '[]', NULL, 0, 1, 1, NULL, 1569341446929, 1569341451839, 0);
INSERT INTO `blog` VALUES (51, 14, '我要学并发-并发问题的源头', '本文从计算机系统层面来讲述在提升性能的过程中，引发的一系列问题。读完本文你将get到并发编程过程中的原子性，可见性，有序性三大问题的来源。', '', '本文从计算机系统层面来讲述在提升性能的过程中，引发的一系列问题。读完本文你将get到并发编程过程中的原子性，可见性，有序性三大问题的来源。\n\n随着硬件发展速度的放缓，摩尔定律已经不在生效，各个硬件似乎已经到了瓶颈；然而随着互联网的普及，网民数量不断增加，对系统的性能带来了巨大的挑战。因此我们要通过各种方式来压榨硬件的性能，从而提高系统的性能进而提升用户体验，提升企业的竞争力。\n\n由于CPU，内存，IO三者之间速度差异，为了提高系统性能，计算机系统对这三者速度进行平衡。\n\n- CPU 增加了缓存，以均衡与内存的速度差异；\n- 操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；\n- 编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。\n\n## 缓存导致得可见性的问题\n\n一个线程对共享变量的修改，另外一个线程能够立刻看到，我们称为可见性。\n\n ![](http://source.mycookies.cn/201910230040_56.png?ERROR)\n\n多核时代，每颗 CPU 都有自己的缓存，这时 CPU 缓存与内存的数据一致性就没那么容易解决了，当多个线程在不同的 CPU 上执行时，这些线程操作的是不同的 CPU 缓存。 \n\n## 线程切换带来的原子性问题\n\n由于IO和cpu执行速度的差别巨大，所以在早期操作系统中就发明的多线程，即使在单核的cpu上我们也可以一遍听着歌，一边写着bug，这就是多线程。\n\n ![](http://source.mycookies.cn/201910230040_569.png)\n\n早期操作系统基于进程来调度cpu， **不同进程间是不共享内存空间**的，所以进程要做任务切换要切换内存映射地址，而一个进程创建的所有线程都是共享一个内存空间，所以线程做任务切换成本很低。现代操作系统都基于更轻量级的线程来调度，现在我们提到的“任务切换”都是指“线程切换”。\n\n因为式单核cpu，所以同一时刻只能执行一个任务，所以多线程通常使用抢占的方式来获取操作系统的时间片。\n ![](http://source.mycookies.cn/201910230040_569.png)\n\nJava的并发编程中都是基于多线程，线程的切换时机通常在一条cpu指令执行完毕之后，而Java作为一门高级编程语言，通常一条语句可能由多个cpu指令来完成。例如：count += 1, 至少需要三条指令。\n\n- 指令1：首先，需要把变量count从该内存加载到cpu的寄存器\n\n- 指令2：之后，在寄存器中执行+1操作\n\n- 指令3： 最后，将结果写入内存（忽略缓存机制）\n  假设count = 0， 有2个线程同时执行count+=1这段代码。线程A执行完指令1将count = 0加载到cpu寄存器，进行了任务切换到了线程B执行，线程B执行完之后将count = 1写入到内存，然后再切换到线程A执行，此时线程A获取寄存器中的count=0进行+1操作得到结果也是1，所以最终内存中的count = 1，而我们所期望的是2.![](http://source.mycookies.cn/201910230042_214.png)\n\n  CPU层面的原子操作仅仅是在指令级别的，既一条cpu指令不可中断。在高级语言中，我们为了避免以上情况发生，**我们把一个或多个操作在cpu执行过程中不被中断的特性称为原子性**。\n\n## 编译优化带来的有序性问题\n\n为了提高程序的执行效率，编译器有时会在编译过程中对程序的进行优化，从而改变程序的执行顺序。如程序“a = 4； b = 5”，在优化后执行顺序可能变成“b = 5； a = 4”。通常进行一项优化过程中可能会带来另一项问题，改变程序的执行顺序通常也会导致让人意想不到的bug。\n\nJava领域中一个经典的案例就是利用双重检查创建单例对象，代码如下：在获取实例getInstance()方法中，我们首先判断instance是否为空，如果为空则锁住Singleton.class对象并再次检查instance是否为空，如果仍然为空则创建Singleton的一个实例。\n\n```java\npublic class Singleton {\nstatic Singleton instance;\n/**\n  * 获取Singleton对象\n  */\npublic static Singleton getInstance(){\n    if (instance == null) {\n        synchronized(Singleton.class) {\n            if (instance == null)\n                instance = new Singleton();\n            }\n        }\n    return instance;\n    }\n}\n```\n\n假设有两个线程 A、B 同时调用 getInstance() 方法，他们会同时发现 instance == null，于是同时对 Singleton.class 加锁，此时 JVM 保证只有一个线程能够加锁成功（假设是线程 A），另外一个线程则会处于等待状态（假设是线程 B）；线程 A 会创建一个 Singleton 实例，之后释放锁，锁释放后，线程 B 被唤醒，线程 B 再次尝试加锁，此时是可以加锁成功的，加锁成功后，线程 B 检查 instance == null 时会发现，已经创建过 Singleton 实例了，所以线程 B 不会再创建一个 Singleton 实例。\n\n以上过程仅仅是我们的理想情况下，但是实际过程中往往会创建多次Singleton实例。原因是创建一个对象需要多条cpu指令，且编译器可能对这几条指令进行了排序。在执行new语句创建一个对象时，通常会包含一下三个步骤（此处进行了简化，实际实现过程会比此过程复杂）：\n\n1. 在堆内存为对象分配一块内存M\n2. 在内存M区域进行Singleton对象的初始化\n3. 将内存M地址赋值给instance变量。\n   但是实际优化后的执行顺序可能时以下这种情况：\n4. 在堆内存为对象分配一块内存M\n5. 将内存M地址赋值给instance变量。\n6. 在内存M区域进行Singleton对象的初始化\n   假设A，B线程同时执行到了getInstance()方法，线程A执行完instance = $M（将内存M地址赋值给instance变量，但是未将对象进行初始化）后切换到B线程，当B线程执行到instance == null时，由于instance已经指向了内存M的地址，所以会返回false，直接返回instance，如果我们这是访问instance中的成员变量或者方法时就可能会出项NullPointException。\n    ![](http://source.mycookies.cn/201910230042_40.png)\n\n# 总结\n\n在操作系统平衡CPU，内存，IO三者速度差异过程中进行了一系列的优化。\n\n- CPU 增加了缓存，以均衡与内存的速度差异；\n- 操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；\n- 编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。\n\n这三个不同方面的优化也带来了可见性，原子性，有序性等问题，他们通常是并发程序的bug的源头。', '<p>本文从计算机系统层面来讲述在提升性能的过程中，引发的一系列问题。读完本文你将get到并发编程过程中的原子性，可见性，有序性三大问题的来源。</p>\n<p>随着硬件发展速度的放缓，摩尔定律已经不在生效，各个硬件似乎已经到了瓶颈；然而随着互联网的普及，网民数量不断增加，对系统的性能带来了巨大的挑战。因此我们要通过各种方式来压榨硬件的性能，从而提高系统的性能进而提升用户体验，提升企业的竞争力。</p>\n<p>由于CPU，内存，IO三者之间速度差异，为了提高系统性能，计算机系统对这三者速度进行平衡。</p>\n<ul>\n<li>CPU 增加了缓存，以均衡与内存的速度差异；</li>\n<li>操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；</li>\n<li>编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。</li>\n</ul>\n<h2><a id=\"_10\"></a>缓存导致得可见性的问题</h2>\n<p>一个线程对共享变量的修改，另外一个线程能够立刻看到，我们称为可见性。</p>\n<p><img src=\"http://source.mycookies.cn/201910230040_56.png?ERROR\" alt=\"\" /></p>\n<p>多核时代，每颗 CPU 都有自己的缓存，这时 CPU 缓存与内存的数据一致性就没那么容易解决了，当多个线程在不同的 CPU 上执行时，这些线程操作的是不同的 CPU 缓存。</p>\n<h2><a id=\"_18\"></a>线程切换带来的原子性问题</h2>\n<p>由于IO和cpu执行速度的差别巨大，所以在早期操作系统中就发明的多线程，即使在单核的cpu上我们也可以一遍听着歌，一边写着bug，这就是多线程。</p>\n<p><img src=\"http://source.mycookies.cn/201910230040_569.png\" alt=\"\" /></p>\n<p>早期操作系统基于进程来调度cpu， <strong>不同进程间是不共享内存空间</strong>的，所以进程要做任务切换要切换内存映射地址，而一个进程创建的所有线程都是共享一个内存空间，所以线程做任务切换成本很低。现代操作系统都基于更轻量级的线程来调度，现在我们提到的“任务切换”都是指“线程切换”。</p>\n<p>因为式单核cpu，所以同一时刻只能执行一个任务，所以多线程通常使用抢占的方式来获取操作系统的时间片。<br />\n<img src=\"http://source.mycookies.cn/201910230040_569.png\" alt=\"\" /></p>\n<p>Java的并发编程中都是基于多线程，线程的切换时机通常在一条cpu指令执行完毕之后，而Java作为一门高级编程语言，通常一条语句可能由多个cpu指令来完成。例如：count += 1, 至少需要三条指令。</p>\n<ul>\n<li>\n<p>指令1：首先，需要把变量count从该内存加载到cpu的寄存器</p>\n</li>\n<li>\n<p>指令2：之后，在寄存器中执行+1操作</p>\n</li>\n<li>\n<p>指令3： 最后，将结果写入内存（忽略缓存机制）<br />\n假设count = 0， 有2个线程同时执行count+=1这段代码。线程A执行完指令1将count = 0加载到cpu寄存器，进行了任务切换到了线程B执行，线程B执行完之后将count = 1写入到内存，然后再切换到线程A执行，此时线程A获取寄存器中的count=0进行+1操作得到结果也是1，所以最终内存中的count = 1，而我们所期望的是2.<img src=\"http://source.mycookies.cn/201910230042_214.png\" alt=\"\" /></p>\n<p>CPU层面的原子操作仅仅是在指令级别的，既一条cpu指令不可中断。在高级语言中，我们为了避免以上情况发生，<strong>我们把一个或多个操作在cpu执行过程中不被中断的特性称为原子性</strong>。</p>\n</li>\n</ul>\n<h2><a id=\"_40\"></a>编译优化带来的有序性问题</h2>\n<p>为了提高程序的执行效率，编译器有时会在编译过程中对程序的进行优化，从而改变程序的执行顺序。如程序“a = 4； b = 5”，在优化后执行顺序可能变成“b = 5； a = 4”。通常进行一项优化过程中可能会带来另一项问题，改变程序的执行顺序通常也会导致让人意想不到的bug。</p>\n<p>Java领域中一个经典的案例就是利用双重检查创建单例对象，代码如下：在获取实例getInstance()方法中，我们首先判断instance是否为空，如果为空则锁住Singleton.class对象并再次检查instance是否为空，如果仍然为空则创建Singleton的一个实例。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n<span class=\"hljs-keyword\">static</span> Singleton instance;\n<span class=\"hljs-comment\">/**\n  * 获取Singleton对象\n  */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span></span>{\n    <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n        <span class=\"hljs-keyword\">synchronized</span>(Singleton.class) {\n            <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>)\n                instance = <span class=\"hljs-keyword\">new</span> Singleton();\n            }\n        }\n    <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n</code></div></pre>\n<p>假设有两个线程 A、B 同时调用 getInstance() 方法，他们会同时发现 instance == null，于是同时对 Singleton.class 加锁，此时 JVM 保证只有一个线程能够加锁成功（假设是线程 A），另外一个线程则会处于等待状态（假设是线程 B）；线程 A 会创建一个 Singleton 实例，之后释放锁，锁释放后，线程 B 被唤醒，线程 B 再次尝试加锁，此时是可以加锁成功的，加锁成功后，线程 B 检查 instance == null 时会发现，已经创建过 Singleton 实例了，所以线程 B 不会再创建一个 Singleton 实例。</p>\n<p>以上过程仅仅是我们的理想情况下，但是实际过程中往往会创建多次Singleton实例。原因是创建一个对象需要多条cpu指令，且编译器可能对这几条指令进行了排序。在执行new语句创建一个对象时，通常会包含一下三个步骤（此处进行了简化，实际实现过程会比此过程复杂）：</p>\n<ol>\n<li>在堆内存为对象分配一块内存M</li>\n<li>在内存M区域进行Singleton对象的初始化</li>\n<li>将内存M地址赋值给instance变量。<br />\n但是实际优化后的执行顺序可能时以下这种情况：</li>\n<li>在堆内存为对象分配一块内存M</li>\n<li>将内存M地址赋值给instance变量。</li>\n<li>在内存M区域进行Singleton对象的初始化<br />\n假设A，B线程同时执行到了getInstance()方法，线程A执行完instance = $M（将内存M地址赋值给instance变量，但是未将对象进行初始化）后切换到B线程，当B线程执行到instance == null时，由于instance已经指向了内存M的地址，所以会返回false，直接返回instance，如果我们这是访问instance中的成员变量或者方法时就可能会出项NullPointException。<br />\n<img src=\"http://source.mycookies.cn/201910230042_40.png\" alt=\"\" /></li>\n</ol>\n<h1><a id=\"_78\"></a>总结</h1>\n<p>在操作系统平衡CPU，内存，IO三者速度差异过程中进行了一系列的优化。</p>\n<ul>\n<li>CPU 增加了缓存，以均衡与内存的速度差异；</li>\n<li>操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；</li>\n<li>编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。</li>\n</ul>\n<p>这三个不同方面的优化也带来了可见性，原子性，有序性等问题，他们通常是并发程序的bug的源头。</p>\n', '[{\"lev\":2,\"text\":\"缓存导致得可见性的问题\",\"id\":\"_10\"},{\"lev\":2,\"text\":\"线程切换带来的原子性问题\",\"id\":\"_18\"},{\"lev\":2,\"text\":\"编译优化带来的有序性问题\",\"id\":\"_40\"},{\"lev\":1,\"text\":\"总结\",\"id\":\"_78\"}]', NULL, 0, 1, 1, NULL, 1571762593221, 1571762622521, 0);
INSERT INTO `blog` VALUES (52, 14, '我要学并发-并发问题的源头', '本文从计算机系统层面来讲述在提升性能的过程中，引发的一系列问题。读完本文你将get到并发编程过程中的原子性，可见性，有序性三大问题的来源。', '201910230040_56.png', '本文从计算机系统层面来讲述在提升性能的过程中，引发的一系列问题。读完本文你将get到并发编程过程中的原子性，可见性，有序性三大问题的来源。\n\n随着硬件发展速度的放缓，摩尔定律已经不在生效，各个硬件似乎已经到了瓶颈；然而随着互联网的普及，网民数量不断增加，对系统的性能带来了巨大的挑战。因此我们要通过各种方式来压榨硬件的性能，从而提高系统的性能进而提升用户体验，提升企业的竞争力。\n\n由于CPU，内存，IO三者之间速度差异，为了提高系统性能，计算机系统对这三者速度进行平衡。\n\n- CPU 增加了缓存，以均衡与内存的速度差异；\n- 操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；\n- 编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。\n\n## 缓存导致得可见性的问题\n\n一个线程对共享变量的修改，另外一个线程能够立刻看到，我们称为可见性。\n\n ![](http://source.mycookies.cn/201910230040_56.png?ERROR)\n\n多核时代，每颗 CPU 都有自己的缓存，这时 CPU 缓存与内存的数据一致性就没那么容易解决了，当多个线程在不同的 CPU 上执行时，这些线程操作的是不同的 CPU 缓存。 \n\n## 线程切换带来的原子性问题\n\n由于IO和cpu执行速度的差别巨大，所以在早期操作系统中就发明的多线程，即使在单核的cpu上我们也可以一遍听着歌，一边写着bug，这就是多线程。\n\n ![](http://source.mycookies.cn/201910230040_569.png)\n\n早期操作系统基于进程来调度cpu， **不同进程间是不共享内存空间**的，所以进程要做任务切换要切换内存映射地址，而一个进程创建的所有线程都是共享一个内存空间，所以线程做任务切换成本很低。现代操作系统都基于更轻量级的线程来调度，现在我们提到的“任务切换”都是指“线程切换”。\n\n因为式单核cpu，所以同一时刻只能执行一个任务，所以多线程通常使用抢占的方式来获取操作系统的时间片。\n ![](http://source.mycookies.cn/201910230040_569.png)\n\nJava的并发编程中都是基于多线程，线程的切换时机通常在一条cpu指令执行完毕之后，而Java作为一门高级编程语言，通常一条语句可能由多个cpu指令来完成。例如：count += 1, 至少需要三条指令。\n\n- 指令1：首先，需要把变量count从该内存加载到cpu的寄存器\n\n- 指令2：之后，在寄存器中执行+1操作\n\n- 指令3： 最后，将结果写入内存（忽略缓存机制）\n  假设count = 0， 有2个线程同时执行count+=1这段代码。线程A执行完指令1将count = 0加载到cpu寄存器，进行了任务切换到了线程B执行，线程B执行完之后将count = 1写入到内存，然后再切换到线程A执行，此时线程A获取寄存器中的count=0进行+1操作得到结果也是1，所以最终内存中的count = 1，而我们所期望的是2.![](http://source.mycookies.cn/201910230042_214.png)\n\n  CPU层面的原子操作仅仅是在指令级别的，既一条cpu指令不可中断。在高级语言中，我们为了避免以上情况发生，**我们把一个或多个操作在cpu执行过程中不被中断的特性称为原子性**。\n\n## 编译优化带来的有序性问题\n\n为了提高程序的执行效率，编译器有时会在编译过程中对程序的进行优化，从而改变程序的执行顺序。如程序“a = 4； b = 5”，在优化后执行顺序可能变成“b = 5； a = 4”。通常进行一项优化过程中可能会带来另一项问题，改变程序的执行顺序通常也会导致让人意想不到的bug。\n\nJava领域中一个经典的案例就是利用双重检查创建单例对象，代码如下：在获取实例getInstance()方法中，我们首先判断instance是否为空，如果为空则锁住Singleton.class对象并再次检查instance是否为空，如果仍然为空则创建Singleton的一个实例。\n```java\npublic class Singleton {\nstatic Singleton instance;\n/**\n  * 获取Singleton对象\n  */\npublic static Singleton getInstance(){\n    if (instance == null) {\n        synchronized(Singleton.class) {\n            if (instance == null)\n                instance = new Singleton();\n            }\n        }\n    return instance;\n    }\n}\n\n```\n\n\n假设有两个线程 A、B 同时调用 getInstance() 方法，他们会同时发现 instance == null，于是同时对 Singleton.class 加锁，此时 JVM 保证只有一个线程能够加锁成功（假设是线程 A），另外一个线程则会处于等待状态（假设是线程 B）；线程 A 会创建一个 Singleton 实例，之后释放锁，锁释放后，线程 B 被唤醒，线程 B 再次尝试加锁，此时是可以加锁成功的，加锁成功后，线程 B 检查 instance == null 时会发现，已经创建过 Singleton 实例了，所以线程 B 不会再创建一个 Singleton 实例。\n\n以上过程仅仅是我们的理想情况下，但是实际过程中往往会创建多次Singleton实例。原因是创建一个对象需要多条cpu指令，且编译器可能对这几条指令进行了排序。在执行new语句创建一个对象时，通常会包含一下三个步骤（此处进行了简化，实际实现过程会比此过程复杂）：\n\n1. 在堆内存为对象分配一块内存M\n2. 在内存M区域进行Singleton对象的初始化\n3. 将内存M地址赋值给instance变量。\n   但是实际优化后的执行顺序可能时以下这种情况：\n4. 在堆内存为对象分配一块内存M\n5. 将内存M地址赋值给instance变量。\n6. 在内存M区域进行Singleton对象的初始化\n   假设A，B线程同时执行到了getInstance()方法，线程A执行完instance = $M（将内存M地址赋值给instance变量，但是未将对象进行初始化）后切换到B线程，当B线程执行到instance == null时，由于instance已经指向了内存M的地址，所以会返回false，直接返回instance，如果我们这是访问instance中的成员变量或者方法时就可能会出项NullPointException。\n    ![](http://source.mycookies.cn/201910230042_40.png)\n\n# 总结\n\n在操作系统平衡CPU，内存，IO三者速度差异过程中进行了一系列的优化。\n\n- CPU 增加了缓存，以均衡与内存的速度差异；\n- 操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；\n- 编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。\n\n这三个不同方面的优化也带来了可见性，原子性，有序性等问题，他们通常是并发程序的bug的源头。', '<p>本文从计算机系统层面来讲述在提升性能的过程中，引发的一系列问题。读完本文你将get到并发编程过程中的原子性，可见性，有序性三大问题的来源。</p>\n<p>随着硬件发展速度的放缓，摩尔定律已经不在生效，各个硬件似乎已经到了瓶颈；然而随着互联网的普及，网民数量不断增加，对系统的性能带来了巨大的挑战。因此我们要通过各种方式来压榨硬件的性能，从而提高系统的性能进而提升用户体验，提升企业的竞争力。</p>\n<p>由于CPU，内存，IO三者之间速度差异，为了提高系统性能，计算机系统对这三者速度进行平衡。</p>\n<ul>\n<li>CPU 增加了缓存，以均衡与内存的速度差异；</li>\n<li>操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；</li>\n<li>编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。</li>\n</ul>\n<h2><a id=\"_10\"></a>缓存导致得可见性的问题</h2>\n<p>一个线程对共享变量的修改，另外一个线程能够立刻看到，我们称为可见性。</p>\n<p><img src=\"http://source.mycookies.cn/201910230040_56.png?ERROR\" alt=\"\" /></p>\n<p>多核时代，每颗 CPU 都有自己的缓存，这时 CPU 缓存与内存的数据一致性就没那么容易解决了，当多个线程在不同的 CPU 上执行时，这些线程操作的是不同的 CPU 缓存。</p>\n<h2><a id=\"_18\"></a>线程切换带来的原子性问题</h2>\n<p>由于IO和cpu执行速度的差别巨大，所以在早期操作系统中就发明的多线程，即使在单核的cpu上我们也可以一遍听着歌，一边写着bug，这就是多线程。</p>\n<p><img src=\"http://source.mycookies.cn/201910230040_569.png\" alt=\"\" /></p>\n<p>早期操作系统基于进程来调度cpu， <strong>不同进程间是不共享内存空间</strong>的，所以进程要做任务切换要切换内存映射地址，而一个进程创建的所有线程都是共享一个内存空间，所以线程做任务切换成本很低。现代操作系统都基于更轻量级的线程来调度，现在我们提到的“任务切换”都是指“线程切换”。</p>\n<p>因为式单核cpu，所以同一时刻只能执行一个任务，所以多线程通常使用抢占的方式来获取操作系统的时间片。<br />\n<img src=\"http://source.mycookies.cn/201910230040_569.png\" alt=\"\" /></p>\n<p>Java的并发编程中都是基于多线程，线程的切换时机通常在一条cpu指令执行完毕之后，而Java作为一门高级编程语言，通常一条语句可能由多个cpu指令来完成。例如：count += 1, 至少需要三条指令。</p>\n<ul>\n<li>\n<p>指令1：首先，需要把变量count从该内存加载到cpu的寄存器</p>\n</li>\n<li>\n<p>指令2：之后，在寄存器中执行+1操作</p>\n</li>\n<li>\n<p>指令3： 最后，将结果写入内存（忽略缓存机制）<br />\n假设count = 0， 有2个线程同时执行count+=1这段代码。线程A执行完指令1将count = 0加载到cpu寄存器，进行了任务切换到了线程B执行，线程B执行完之后将count = 1写入到内存，然后再切换到线程A执行，此时线程A获取寄存器中的count=0进行+1操作得到结果也是1，所以最终内存中的count = 1，而我们所期望的是2.<img src=\"http://source.mycookies.cn/201910230042_214.png\" alt=\"\" /></p>\n<p>CPU层面的原子操作仅仅是在指令级别的，既一条cpu指令不可中断。在高级语言中，我们为了避免以上情况发生，<strong>我们把一个或多个操作在cpu执行过程中不被中断的特性称为原子性</strong>。</p>\n</li>\n</ul>\n<h2><a id=\"_40\"></a>编译优化带来的有序性问题</h2>\n<p>为了提高程序的执行效率，编译器有时会在编译过程中对程序的进行优化，从而改变程序的执行顺序。如程序“a = 4； b = 5”，在优化后执行顺序可能变成“b = 5； a = 4”。通常进行一项优化过程中可能会带来另一项问题，改变程序的执行顺序通常也会导致让人意想不到的bug。</p>\n<p>Java领域中一个经典的案例就是利用双重检查创建单例对象，代码如下：在获取实例getInstance()方法中，我们首先判断instance是否为空，如果为空则锁住Singleton.class对象并再次检查instance是否为空，如果仍然为空则创建Singleton的一个实例。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Singleton</span> </span>{\n<span class=\"hljs-keyword\">static</span> Singleton instance;\n<span class=\"hljs-comment\">/**\n  * 获取Singleton对象\n  */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> Singleton <span class=\"hljs-title\">getInstance</span><span class=\"hljs-params\">()</span></span>{\n    <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>) {\n        <span class=\"hljs-keyword\">synchronized</span>(Singleton.class) {\n            <span class=\"hljs-keyword\">if</span> (instance == <span class=\"hljs-keyword\">null</span>)\n                instance = <span class=\"hljs-keyword\">new</span> Singleton();\n            }\n        }\n    <span class=\"hljs-keyword\">return</span> instance;\n    }\n}\n\n</code></div></pre>\n<p>假设有两个线程 A、B 同时调用 getInstance() 方法，他们会同时发现 instance == null，于是同时对 Singleton.class 加锁，此时 JVM 保证只有一个线程能够加锁成功（假设是线程 A），另外一个线程则会处于等待状态（假设是线程 B）；线程 A 会创建一个 Singleton 实例，之后释放锁，锁释放后，线程 B 被唤醒，线程 B 再次尝试加锁，此时是可以加锁成功的，加锁成功后，线程 B 检查 instance == null 时会发现，已经创建过 Singleton 实例了，所以线程 B 不会再创建一个 Singleton 实例。</p>\n<p>以上过程仅仅是我们的理想情况下，但是实际过程中往往会创建多次Singleton实例。原因是创建一个对象需要多条cpu指令，且编译器可能对这几条指令进行了排序。在执行new语句创建一个对象时，通常会包含一下三个步骤（此处进行了简化，实际实现过程会比此过程复杂）：</p>\n<ol>\n<li>在堆内存为对象分配一块内存M</li>\n<li>在内存M区域进行Singleton对象的初始化</li>\n<li>将内存M地址赋值给instance变量。<br />\n但是实际优化后的执行顺序可能时以下这种情况：</li>\n<li>在堆内存为对象分配一块内存M</li>\n<li>将内存M地址赋值给instance变量。</li>\n<li>在内存M区域进行Singleton对象的初始化<br />\n假设A，B线程同时执行到了getInstance()方法，线程A执行完instance = $M（将内存M地址赋值给instance变量，但是未将对象进行初始化）后切换到B线程，当B线程执行到instance == null时，由于instance已经指向了内存M的地址，所以会返回false，直接返回instance，如果我们这是访问instance中的成员变量或者方法时就可能会出项NullPointException。<br />\n<img src=\"http://source.mycookies.cn/201910230042_40.png\" alt=\"\" /></li>\n</ol>\n<h1><a id=\"_79\"></a>总结</h1>\n<p>在操作系统平衡CPU，内存，IO三者速度差异过程中进行了一系列的优化。</p>\n<ul>\n<li>CPU 增加了缓存，以均衡与内存的速度差异；</li>\n<li>操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；</li>\n<li>编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。</li>\n</ul>\n<p>这三个不同方面的优化也带来了可见性，原子性，有序性等问题，他们通常是并发程序的bug的源头。</p>\n', '[{\"lev\":2,\"text\":\"缓存导致得可见性的问题\",\"id\":\"_10\"},{\"lev\":2,\"text\":\"线程切换带来的原子性问题\",\"id\":\"_18\"},{\"lev\":2,\"text\":\"编译优化带来的有序性问题\",\"id\":\"_40\"},{\"lev\":1,\"text\":\"总结\",\"id\":\"_79\"}]', NULL, 0, 178, 11, NULL, 1571762606687, 1571763394006, 1);
INSERT INTO `blog` VALUES (53, 14, '我要学并发-Java内存模型到底是什么', 'Java内存模型到底是个什么东西，晦涩难懂的happens-before又表示什么意思，synchronized，volatile，final又能做些什么，一文搞懂!', '201910242259_360.png', '# 内存模型\n\n在计算机CPU，内存，IO三者之间速度差异，为了提高系统性能，对这三者速度进行平衡。\n\n- CPU 增加了缓存，以均衡与内存的速度差异；\n- 操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；\n- 编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。\n\n以上三种系统优化，对于硬件的效率有了显著的提升，但是他们同时也带来了可见性，原子性以及顺序性等问题。基于Cpu高速缓存的存储交互很好得解决了CPU和内存得速度矛盾，但是也提高了计算机系统得复杂度，引入了新的问题：缓存一致性（Cache Coherence）。\n\n每个处理器都有自己独享得高速缓存，多个处理器共享系统主内存，当多个处理器运算任务涉及到同一块主内存区域时，将可能会导致数据不一致，这时以谁的数据为准就成了问题。为了解决一致性问题，各个处理器需要遵守一些协议，根据这些协议来进行读写操作。所以**内存模型可以理解为是为了解决缓存一致性问题，在特定的操作协议下，对特定的内存或高速缓存进行读写的过程的抽象。**\n ![](http://source.mycookies.cn/201910242259_360.png)\n\n# Java内存模型\n\n## JMM的作用\n\nJava虚拟机规范试图定义一种Java内存模型（Java Memory Model, JMM），**用来屏蔽掉硬件和操作系统的内存访问差异，以实现让Java程序在各种平台都能达到一致的内存访问效果**。使得Java程序员可以忽略不同处理器平台的不同内存模型，而只需要关心JMM即可。\n ![](http://source.mycookies.cn/201910242259_858.png?ERROR)\n\n## JMM抽象结构\n\n### JMM 抽象结构图\n\nJMM借鉴了处理器内存模型的思想，从抽象的角度来看，JMM定义了线程和主内存之间的抽象关系，它涵盖了缓存，写缓冲区，寄存器以及其他硬件和编译器优化。下图是JMM的抽象结构示意图。\n\n ![](http://source.mycookies.cn/201910242300_551.png)\n\n### JMM中线程间通信\n\n并发编程中需要考虑的两个核心问题：线程之间如何通信（可见性和有序性）以及线程之间如何同步（原子性）。通信是指线程之间以何种方式进行信息交换；同步是指程序中用于控制不同线程间操作发生的相对顺序\n ![](http://source.mycookies.cn/201910242300_89.png?ERROR)\n\nJMM规定了程序中所有的变量（实例字段，静态字段，构成数组对象的元素等）都存储在主内存中；它的主要目标是定义程序种各个变量的访问规则，既从虚拟机将变量存储到内存和从内存种取出变量这样的底层细节。每个线程都有自己的本地内存，线程之间在JMM控制协议的限制下通过主内存进行通信。假设由两个线程A和B，线程A要给线程B发送\"hello\"消息，下图是两个线程进行通信的过程：\n ![](http://source.mycookies.cn/201910242301_497.png?ERROR)\n由图可见，假设线程A要发消息给线程B，那么它必须经过两个步骤：\n\n1. 线程A把本地内存中的共享变量副本message更新后刷新到主内存中\n2. 线程B到主内存取读取线程A更新的共享变量message\n\n## JMM的设计与实现\n\nJMM相关的协议比较复杂，我们可以从编译器或者JVM工程师，以及Java工程师来进行学习。本文仅从Java工程师角度来进行探讨Java中通过那些协议来控制JMM，从而保证数据一致性。\n\nJMM的实现可以分为两部分，包括happen-before规则以及一系列的关键字。它的核心目标就是确保编译器，各平台的处理器都能提供一致的行为，在内存中表现出一致性的结果。具体来讲就是通过happens-before规则以及volatile，synchronized，final关键字解决可见性，原子性以及有序性问题，从而保证内存中数据的一致性。\n![](http://source.mycookies.cn/201910242301_490.png?ERROR)\n\n### Happens-Before规则\n\nhappens-before是JMM中最核心的概念，happens-before用来指定两个操作之间的执行顺序，这两个操作可以在一个线程内，也可以在不同的线程内，因此JMM通过happen-before关系向程序员提供跨线程的内存可见性保证，JMM的具体定义如下：\n\n1. 如果一个操作happens-before另一个操作，那么第一个操作的执行结果将对第二个操作可见，而且第一个操作的执行顺序排在第二个操作之前。\n2. 两个操作存在着happen-before关系，并不意味着Java平台具体实现必须要按照happen-before关系指定的顺序来执行。如果重排序之后的执行结果，与按照happen-before关系来执行的结果一致，那么这种重排序不非法（也就是说，JMM允许这种重排序）\n\n下面的示例代码，假设线程 A 执行 writer() 方法，线程 B 执行 reader() 方法，如果线程 B 看到 “v == true” 时，那么线程 B 看到的变量 x 是多少呢？ \n\n``` java\nclass VolatileExample {\n  int x = 0;\n  volatile boolean v = false;\n  public void writer() {\n    x = 42;					// 1\n    v = true;				// 2\n  }\n  public void reader() {\n    if (v == true) {		// 3\n      // 这里 x 会是多少呢？  // 4\n    }\n  }\n}\n```\n\n\n\n#### 1. 程序顺序性规则\n\n程序顺序规则（Program Order Rule）: 一个线程内的每个操作，按照代码先后顺序，书写在前面的代码先行发生于与写在后面的操作。\n\n#### 2. volatile变量规则\n\nvolatile变量规则（Volatile Variable Rule）：对于一个volatile修饰得变量得写操作先行发生于后面对这个变量得读操作。“后面”指得是时间上的顺序\n\n#### 3. 传递性规则\n\n传递性规则（Transitivity）： 如果操作A先行发生于操作B， 操作B先行发生于操作C，那么A先行发生于操作C。\n\n针对上述的1，2，3项happens-before我们作出个总结，下图是我们根据volatile读写建立的happens-before关系图。\n\n![](http://source.mycookies.cn/201910250033_868.png?ERROR)\n\n#### 4. 程锁定规则\n\n管程锁定规则（Monitor Lock Rule）: 一个unlock操作先行发生于后面对这个锁得lock操作。“后面”指得是时间上的顺序\n\n![](http://source.mycookies.cn/201910250011_14.png)\n\n在之前文章[并发问题的源头](http://www.mycookies.cn/#/blog/detail/52)中并发问题中count++的问题提到了线程切换导致计数出现问题，在此我们就可以尝试利用happens-before规则解决这个原子性问题。\n\n```java  \npublic class SafeCounter {\n  private long count = 0L;\n  public long get() {\n    return cout;\n  }\n  public synchronized void addOne() {\n    count++;\n  }\n}\n```\n\n上述代码真的解决可以解决问题吗？\n\n#### 4. 线程启动规则\n\n线程启动规则（Thread Start Rule）: Thread对象的start（）方法，先行发生于此线程的每一个动作。\n\n ![](http://source.mycookies.cn/201910250038_147.png?ERROR)\n\n#### 6.线程终止规则\n\n线程终止规则（Thread Termination Rule）: 线程中的所有操作都先行发生于对于此线程的终止检测，我们可以通过Thread.join()方法结束，Thread.isAlive()返回值等手段来检测线程是否执行完毕。\n\n![](http://source.mycookies.cn/201910250041_170.png?ERROR)\n\n#### 7. 线程中断规则 \n\n线程中断规则（Thread Interruption Rule）: 对线程的interrupt（）方法的调用先行发生于被中断线程代码检测到中断事件的发生，可以通过Thread.interrupted()方法检测到是否有中断发生。\n\n#### 8. 对象终结规则\n\n对象终结规则（Finalizer Rule）： 一个对象的初始化完成（构造函数执行完毕）先行发生于它的finalize()方法。\n\nhappens-before规则一共可分为以上8条，笔者只针对在并发编程中常见的前6项进行了详细介绍，具体内容可以参考http://gee.cs.oswego.edu/dl/jmm/cookbook.html。在JMM中，我认为这些规则也是比较难以理解的概念。总结下来happens-before规则强调的是一种可见性关系，事件A happens-before B，意味着A事件对于B事件是可见的，无论事件A和事件B是否发生在一个线程里。\n\n### volatile关键字\n\n**volatile自身特性**\n\n1. 可见性：对一个volatile变量的读，总能看到（任意线程）对这个volatile变量最后的写入。\n2. 原子性： 对单个volatile变量的读/写具有原子性，注意，对于类似于vaolatile ++ 这种操作不具有原子性，因为这个操作是个符合操作。\n\n**volatile在JMM中表现出的内存语义**\n\n1. 当写一个变量时，JMM会把该线程对应的本地内存中的共享变量刷新到主内存。\n2. 当读一个volatile变量时，JMM会把该线程对应的本地内存置为无效。接下来将从主内存中读取共享变量。\n\nvolatile是java中提供用来解决可见性问题得关键字，可以理解为jvm看见volatile关键字修饰的变量时，会“禁用缓存”既线程的本地内存，每次对此类型变量的读操作时都会从主内存中重新读取到本地内存中，每次写操作也会立刻同步到主内存中，这也正进一步诠释了volatile变量规则中描述的，对于一个volatile修饰得变量得写操作先行发生于后面对这个变量得读操作；被volatile修饰的共享变量，会被禁用某些类型的指令重排序，来保证顺序性问题。\n\n### synchronized-万能的锁\n\n由管程锁定规则，一个unlock操作先行发生于后面对这个锁的lock操作。在Java中通过管程（Monitor）来解决原子性问题，具体的表现为Synchronized关键字。被synchronized修饰的代码块在编译时会在开始位置和结束位置插入monitorenter和monitorexit指令，JVM保证monitorenter和monitorexit与之与之配对，并且这段代码得原子性。synchronized中的lock和unlock操作是隐式进行的，在java中我们不仅可以使用synchronized关键字，同样可以使用各种实现了Lock接口的锁来实现。\n\n**synchronized的内存语义**\n\n1. 当线程获取锁时，会把线程本地内存置为无效\n2. 当线程释放锁时，会将共享变量刷新到主内存中\n\n### final-默默无闻的优化\n\n在并发编程中的原子性，可见性以及顺序性的问题导致的根本就是共享变量的改变。final关键字解决并发问题的方式是从源头下手，让变量不可变，变量被final修饰表示当前变量不会发生改变，编译器可以放心进行优化。\n\n# 总结\n\n1. JMM是用来屏蔽掉硬件和操作系统的内存访问差异，以实现让Java程序在各种平台都能达到一致的内存访问效果\n2. 站在称序员角度来看JMM是一系列的协议（hanppens-before规则）和一些关键字，Synchronized，volatile和final\n3. volatile通过禁用缓存和编译优化保证了顺序性和可见性\n4. synchronzed能保证程序执行的原子性，可见性和有序性，是并发中的万能要是\n5. final关键字修饰的变量 不可变\n\n## Q&A\n\n上文中尝试用synchronized解决count++的问题，为了方便观察将代码copy到此处，这段代码有没有什么不对劲呢？可以在留言区说出你的想法，我们一起来学习！\n\n```java \npublic class SafeCounter {\n  private long count = 0L;\n  public long get() {\n    return cout;\n  }\n  public synchronized void addOne() {\n    count++;\n  }\n}\n```\n\n', '<h1><a id=\"_0\"></a>内存模型</h1>\n<p>在计算机CPU，内存，IO三者之间速度差异，为了提高系统性能，对这三者速度进行平衡。</p>\n<ul>\n<li>CPU 增加了缓存，以均衡与内存的速度差异；</li>\n<li>操作系统增加了进程、线程，以分时复用 CPU，进而均衡 CPU 与 I/O 设备的速度差异；</li>\n<li>编译程序优化指令执行次序，使得缓存能够得到更加合理地利用。</li>\n</ul>\n<p>以上三种系统优化，对于硬件的效率有了显著的提升，但是他们同时也带来了可见性，原子性以及顺序性等问题。基于Cpu高速缓存的存储交互很好得解决了CPU和内存得速度矛盾，但是也提高了计算机系统得复杂度，引入了新的问题：缓存一致性（Cache Coherence）。</p>\n<p>每个处理器都有自己独享得高速缓存，多个处理器共享系统主内存，当多个处理器运算任务涉及到同一块主内存区域时，将可能会导致数据不一致，这时以谁的数据为准就成了问题。为了解决一致性问题，各个处理器需要遵守一些协议，根据这些协议来进行读写操作。所以<strong>内存模型可以理解为是为了解决缓存一致性问题，在特定的操作协议下，对特定的内存或高速缓存进行读写的过程的抽象。</strong><br />\n<img src=\"http://source.mycookies.cn/201910242259_360.png\" alt=\"\" /></p>\n<h1><a id=\"Java_13\"></a>Java内存模型</h1>\n<h2><a id=\"JMM_15\"></a>JMM的作用</h2>\n<p>Java虚拟机规范试图定义一种Java内存模型（Java Memory Model, JMM），<strong>用来屏蔽掉硬件和操作系统的内存访问差异，以实现让Java程序在各种平台都能达到一致的内存访问效果</strong>。使得Java程序员可以忽略不同处理器平台的不同内存模型，而只需要关心JMM即可。<br />\n<img src=\"http://source.mycookies.cn/201910242259_858.png?ERROR\" alt=\"\" /></p>\n<h2><a id=\"JMM_20\"></a>JMM抽象结构</h2>\n<h3><a id=\"JMM__22\"></a>JMM 抽象结构图</h3>\n<p>JMM借鉴了处理器内存模型的思想，从抽象的角度来看，JMM定义了线程和主内存之间的抽象关系，它涵盖了缓存，写缓冲区，寄存器以及其他硬件和编译器优化。下图是JMM的抽象结构示意图。</p>\n<p><img src=\"http://source.mycookies.cn/201910242300_551.png\" alt=\"\" /></p>\n<h3><a id=\"JMM_28\"></a>JMM中线程间通信</h3>\n<p>并发编程中需要考虑的两个核心问题：线程之间如何通信（可见性和有序性）以及线程之间如何同步（原子性）。通信是指线程之间以何种方式进行信息交换；同步是指程序中用于控制不同线程间操作发生的相对顺序<br />\n<img src=\"http://source.mycookies.cn/201910242300_89.png?ERROR\" alt=\"\" /></p>\n<p>JMM规定了程序中所有的变量（实例字段，静态字段，构成数组对象的元素等）都存储在主内存中；它的主要目标是定义程序种各个变量的访问规则，既从虚拟机将变量存储到内存和从内存种取出变量这样的底层细节。每个线程都有自己的本地内存，线程之间在JMM控制协议的限制下通过主内存进行通信。假设由两个线程A和B，线程A要给线程B发送&quot;hello&quot;消息，下图是两个线程进行通信的过程：<br />\n<img src=\"http://source.mycookies.cn/201910242301_497.png?ERROR\" alt=\"\" /><br />\n由图可见，假设线程A要发消息给线程B，那么它必须经过两个步骤：</p>\n<ol>\n<li>线程A把本地内存中的共享变量副本message更新后刷新到主内存中</li>\n<li>线程B到主内存取读取线程A更新的共享变量message</li>\n</ol>\n<h2><a id=\"JMM_40\"></a>JMM的设计与实现</h2>\n<p>JMM相关的协议比较复杂，我们可以从编译器或者JVM工程师，以及Java工程师来进行学习。本文仅从Java工程师角度来进行探讨Java中通过那些协议来控制JMM，从而保证数据一致性。</p>\n<p>JMM的实现可以分为两部分，包括happen-before规则以及一系列的关键字。它的核心目标就是确保编译器，各平台的处理器都能提供一致的行为，在内存中表现出一致性的结果。具体来讲就是通过happens-before规则以及volatile，synchronized，final关键字解决可见性，原子性以及有序性问题，从而保证内存中数据的一致性。<br />\n<img src=\"http://source.mycookies.cn/201910242301_490.png?ERROR\" alt=\"\" /></p>\n<h3><a id=\"HappensBefore_47\"></a>Happens-Before规则</h3>\n<p>happens-before是JMM中最核心的概念，happens-before用来指定两个操作之间的执行顺序，这两个操作可以在一个线程内，也可以在不同的线程内，因此JMM通过happen-before关系向程序员提供跨线程的内存可见性保证，JMM的具体定义如下：</p>\n<ol>\n<li>如果一个操作happens-before另一个操作，那么第一个操作的执行结果将对第二个操作可见，而且第一个操作的执行顺序排在第二个操作之前。</li>\n<li>两个操作存在着happen-before关系，并不意味着Java平台具体实现必须要按照happen-before关系指定的顺序来执行。如果重排序之后的执行结果，与按照happen-before关系来执行的结果一致，那么这种重排序不非法（也就是说，JMM允许这种重排序）</li>\n</ol>\n<p>下面的示例代码，假设线程 A 执行 writer() 方法，线程 B 执行 reader() 方法，如果线程 B 看到 “v == true” 时，那么线程 B 看到的变量 x 是多少呢？</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">VolatileExample</span> </span>{\n  <span class=\"hljs-keyword\">int</span> x = <span class=\"hljs-number\">0</span>;\n  <span class=\"hljs-keyword\">volatile</span> <span class=\"hljs-keyword\">boolean</span> v = <span class=\"hljs-keyword\">false</span>;\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">writer</span><span class=\"hljs-params\">()</span> </span>{\n    x = <span class=\"hljs-number\">42</span>;					<span class=\"hljs-comment\">// 1</span>\n    v = <span class=\"hljs-keyword\">true</span>;				<span class=\"hljs-comment\">// 2</span>\n  }\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">reader</span><span class=\"hljs-params\">()</span> </span>{\n    <span class=\"hljs-keyword\">if</span> (v == <span class=\"hljs-keyword\">true</span>) {		<span class=\"hljs-comment\">// 3</span>\n      <span class=\"hljs-comment\">// 这里 x 会是多少呢？  // 4</span>\n    }\n  }\n}\n</code></div></pre>\n<h4><a id=\"1__74\"></a>1. 程序顺序性规则</h4>\n<p>程序顺序规则（Program Order Rule）: 一个线程内的每个操作，按照代码先后顺序，书写在前面的代码先行发生于与写在后面的操作。</p>\n<h4><a id=\"2_volatile_78\"></a>2. volatile变量规则</h4>\n<p>volatile变量规则（Volatile Variable Rule）：对于一个volatile修饰得变量得写操作先行发生于后面对这个变量得读操作。“后面”指得是时间上的顺序</p>\n<h4><a id=\"3__82\"></a>3. 传递性规则</h4>\n<p>传递性规则（Transitivity）： 如果操作A先行发生于操作B， 操作B先行发生于操作C，那么A先行发生于操作C。</p>\n<p>针对上述的1，2，3项happens-before我们作出个总结，下图是我们根据volatile读写建立的happens-before关系图。</p>\n<p><img src=\"http://source.mycookies.cn/201910250033_868.png?ERROR\" alt=\"\" /></p>\n<h4><a id=\"4__90\"></a>4. 程锁定规则</h4>\n<p>管程锁定规则（Monitor Lock Rule）: 一个unlock操作先行发生于后面对这个锁得lock操作。“后面”指得是时间上的顺序</p>\n<p><img src=\"http://source.mycookies.cn/201910250011_14.png\" alt=\"\" /></p>\n<p>在之前文章<a href=\"http://www.mycookies.cn/#/blog/detail/52\" target=\"_blank\">并发问题的源头</a>中并发问题中count++的问题提到了线程切换导致计数出现问题，在此我们就可以尝试利用happens-before规则解决这个原子性问题。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">SafeCounter</span> </span>{\n  <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">long</span> count = <span class=\"hljs-number\">0L</span>;\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">long</span> <span class=\"hljs-title\">get</span><span class=\"hljs-params\">()</span> </span>{\n    <span class=\"hljs-keyword\">return</span> cout;\n  }\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">synchronized</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">addOne</span><span class=\"hljs-params\">()</span> </span>{\n    count++;\n  }\n}\n</code></div></pre>\n<p>上述代码真的解决可以解决问题吗？</p>\n<h4><a id=\"4__112\"></a>4. 线程启动规则</h4>\n<p>线程启动规则（Thread Start Rule）: Thread对象的start（）方法，先行发生于此线程的每一个动作。</p>\n<p><img src=\"http://source.mycookies.cn/201910250038_147.png?ERROR\" alt=\"\" /></p>\n<h4><a id=\"6_118\"></a>6.线程终止规则</h4>\n<p>线程终止规则（Thread Termination Rule）: 线程中的所有操作都先行发生于对于此线程的终止检测，我们可以通过Thread.join()方法结束，Thread.isAlive()返回值等手段来检测线程是否执行完毕。</p>\n<p><img src=\"http://source.mycookies.cn/201910250041_170.png?ERROR\" alt=\"\" /></p>\n<h4><a id=\"7__124\"></a>7. 线程中断规则</h4>\n<p>线程中断规则（Thread Interruption Rule）: 对线程的interrupt（）方法的调用先行发生于被中断线程代码检测到中断事件的发生，可以通过Thread.interrupted()方法检测到是否有中断发生。</p>\n<h4><a id=\"8__128\"></a>8. 对象终结规则</h4>\n<p>对象终结规则（Finalizer Rule）： 一个对象的初始化完成（构造函数执行完毕）先行发生于它的finalize()方法。</p>\n<p>happens-before规则一共可分为以上8条，笔者只针对在并发编程中常见的前6项进行了详细介绍，具体内容可以参考http://gee.cs.oswego.edu/dl/jmm/cookbook.html。在JMM中，我认为这些规则也是比较难以理解的概念。总结下来happens-before规则强调的是一种可见性关系，事件A happens-before B，意味着A事件对于B事件是可见的，无论事件A和事件B是否发生在一个线程里。</p>\n<h3><a id=\"volatile_134\"></a>volatile关键字</h3>\n<p><strong>volatile自身特性</strong></p>\n<ol>\n<li>可见性：对一个volatile变量的读，总能看到（任意线程）对这个volatile变量最后的写入。</li>\n<li>原子性： 对单个volatile变量的读/写具有原子性，注意，对于类似于vaolatile ++ 这种操作不具有原子性，因为这个操作是个符合操作。</li>\n</ol>\n<p><strong>volatile在JMM中表现出的内存语义</strong></p>\n<ol>\n<li>当写一个变量时，JMM会把该线程对应的本地内存中的共享变量刷新到主内存。</li>\n<li>当读一个volatile变量时，JMM会把该线程对应的本地内存置为无效。接下来将从主内存中读取共享变量。</li>\n</ol>\n<p>volatile是java中提供用来解决可见性问题得关键字，可以理解为jvm看见volatile关键字修饰的变量时，会“禁用缓存”既线程的本地内存，每次对此类型变量的读操作时都会从主内存中重新读取到本地内存中，每次写操作也会立刻同步到主内存中，这也正进一步诠释了volatile变量规则中描述的，对于一个volatile修饰得变量得写操作先行发生于后面对这个变量得读操作；被volatile修饰的共享变量，会被禁用某些类型的指令重排序，来保证顺序性问题。</p>\n<h3><a id=\"synchronized_148\"></a>synchronized-万能的锁</h3>\n<p>由管程锁定规则，一个unlock操作先行发生于后面对这个锁的lock操作。在Java中通过管程（Monitor）来解决原子性问题，具体的表现为Synchronized关键字。被synchronized修饰的代码块在编译时会在开始位置和结束位置插入monitorenter和monitorexit指令，JVM保证monitorenter和monitorexit与之与之配对，并且这段代码得原子性。synchronized中的lock和unlock操作是隐式进行的，在java中我们不仅可以使用synchronized关键字，同样可以使用各种实现了Lock接口的锁来实现。</p>\n<p><strong>synchronized的内存语义</strong></p>\n<ol>\n<li>当线程获取锁时，会把线程本地内存置为无效</li>\n<li>当线程释放锁时，会将共享变量刷新到主内存中</li>\n</ol>\n<h3><a id=\"final_157\"></a>final-默默无闻的优化</h3>\n<p>在并发编程中的原子性，可见性以及顺序性的问题导致的根本就是共享变量的改变。final关键字解决并发问题的方式是从源头下手，让变量不可变，变量被final修饰表示当前变量不会发生改变，编译器可以放心进行优化。</p>\n<h1><a id=\"_161\"></a>总结</h1>\n<ol>\n<li>JMM是用来屏蔽掉硬件和操作系统的内存访问差异，以实现让Java程序在各种平台都能达到一致的内存访问效果</li>\n<li>站在称序员角度来看JMM是一系列的协议（hanppens-before规则）和一些关键字，Synchronized，volatile和final</li>\n<li>volatile通过禁用缓存和编译优化保证了顺序性和可见性</li>\n<li>synchronzed能保证程序执行的原子性，可见性和有序性，是并发中的万能要是</li>\n<li>final关键字修饰的变量 不可变</li>\n</ol>\n<h2><a id=\"QA_169\"></a>Q&amp;A</h2>\n<p>上文中尝试用synchronized解决count++的问题，为了方便观察将代码copy到此处，这段代码有没有什么不对劲呢？可以在留言区说出你的想法，我们一起来学习！</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">SafeCounter</span> </span>{\n  <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">long</span> count = <span class=\"hljs-number\">0L</span>;\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">long</span> <span class=\"hljs-title\">get</span><span class=\"hljs-params\">()</span> </span>{\n    <span class=\"hljs-keyword\">return</span> cout;\n  }\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">synchronized</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">addOne</span><span class=\"hljs-params\">()</span> </span>{\n    count++;\n  }\n}\n</code></div></pre>\n', '[{\"lev\":1,\"text\":\"内存模型\",\"id\":\"_0\"},{\"lev\":1,\"text\":\"Java内存模型\",\"id\":\"Java_13\"},{\"lev\":2,\"text\":\"JMM的作用\",\"id\":\"JMM_15\"},{\"lev\":2,\"text\":\"JMM抽象结构\",\"id\":\"JMM_20\"},{\"lev\":3,\"text\":\"JMM 抽象结构图\",\"id\":\"JMM__22\"},{\"lev\":3,\"text\":\"JMM中线程间通信\",\"id\":\"JMM_28\"},{\"lev\":2,\"text\":\"JMM的设计与实现\",\"id\":\"JMM_40\"},{\"lev\":3,\"text\":\"Happens-Before规则\",\"id\":\"HappensBefore_47\"},{\"lev\":4,\"text\":\"1. 程序顺序性规则\",\"id\":\"1__74\"},{\"lev\":4,\"text\":\"2. volatile变量规则\",\"id\":\"2_volatile_78\"},{\"lev\":4,\"text\":\"3. 传递性规则\",\"id\":\"3__82\"},{\"lev\":4,\"text\":\"4. 程锁定规则\",\"id\":\"4__90\"},{\"lev\":4,\"text\":\"4. 线程启动规则\",\"id\":\"4__112\"},{\"lev\":4,\"text\":\"6.线程终止规则\",\"id\":\"6_118\"},{\"lev\":4,\"text\":\"7. 线程中断规则\",\"id\":\"7__124\"},{\"lev\":4,\"text\":\"8. 对象终结规则\",\"id\":\"8__128\"},{\"lev\":3,\"text\":\"volatile关键字\",\"id\":\"volatile_134\"},{\"lev\":3,\"text\":\"synchronized-万能的锁\",\"id\":\"synchronized_148\"},{\"lev\":3,\"text\":\"final-默默无闻的优化\",\"id\":\"final_157\"},{\"lev\":1,\"text\":\"总结\",\"id\":\"_161\"},{\"lev\":2,\"text\":\"Q&A\",\"id\":\"QA_169\"}]', NULL, 0, 261, 3, NULL, 1571935939585, 1571935979163, 1);
INSERT INTO `blog` VALUES (54, 14, 'Java中的锁相关概念', '在Java的学习过程中想必大家都听说过一系列关于锁的问题；互斥锁，偏向锁，自旋轻量级锁，读写锁等概念让人感到头疼，本文将从不同维度对各种锁的概念进行分类讲解，目的是能够把每种锁的概念说清楚，以及在java中是怎么实现的。', '', '\n\n## 乐观/悲观锁\n顾名思义，乐观锁和悲观所正如名称一样。悲观锁是一种悲观的锁，每次读写数据都会加锁。\n而乐观锁则相反，每次读写数据都假设数据不会被修改，也不会加锁；而是通过一种验证机制来确保数据的一致性，既在更新的时候用预期值和原始值来判断在执行修改期间是否发生了变化。乐观锁通常使用对数据增加版本号的方式来实现乐观锁。\nJava中的乐观锁主要是通过CAS机制实现，juc包下实现了Lock接口的类都属于乐观锁，而Synchronized关键字则是典型的悲观锁。\n\n\n## 显式/隐式锁\n隐式锁和显式锁的区别在于是否需要手动加锁和释放锁，在Java中的隐式锁为Synchronized关键字，见\nhttp://www.mycookies.cn/#/blog/detail/53；而显式锁需要手动lock和unlock，java中主要表现在Lock接口的实现类，如ReentrantLock。\ntodo: 代码块\n\n## 公平/非公平锁\n公平锁是指多个线程按照申请锁的顺序来获取锁，线程直接进入队列中排队，队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低，等待队列中除第一个线程以外的所有线程都会阻塞，CPU唤醒阻塞线程的开销比非公平锁大。\nJava中的非公平锁主要体现在Synchronized关键字，而公平锁仅在ReentrantLock中才有实现，ReentrantLock支持公平锁和非公平锁两种模式。\ntodo: 图\n## 可重入/不可冲入锁\n可重入锁又名递归锁，是指在同一个线程在外层方法获取锁的时候，再进入该线程的内层方法会自动获取锁（前提锁对象得是同一个对象或者class），不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁，可重入锁的一个优点是可一定程度避免死锁。\n当然也可以通过代码将锁设置为不可重入。\n```java\npublic class Lock {\n    private boolean isLocked = false;\n    public synchronized void lock() throws InterruptedException{\n        while(isLocked){\n            wait();\n        }\n        isLocked = true;\n    }\n    public synchronized void unlock(){\n        isLocked = false;\n        notifyAll();\n    }\n}\n\n```\n\n\n## 共享锁/排他锁\n共享锁：多个线程可以同时对同一资源加共享锁，获取共享锁后只能对数据进行读取操作。\n排他锁：同一时间只能由一个线程对资源加排他锁，能读锁定资源进行读写操作。\n共享锁和排他锁在java中主要表现在ReadWriteLock，只有ReadLock时共享锁，其他类型的锁都是排他锁。\n', '<h2><a id=\"_2\"></a>乐观/悲观锁</h2>\n<p>顾名思义，乐观锁和悲观所正如名称一样。悲观锁是一种悲观的锁，每次读写数据都会加锁。<br />\n而乐观锁则相反，每次读写数据都假设数据不会被修改，也不会加锁；而是通过一种验证机制来确保数据的一致性，既在更新的时候用预期值和原始值来判断在执行修改期间是否发生了变化。乐观锁通常使用对数据增加版本号的方式来实现乐观锁。<br />\nJava中的乐观锁主要是通过CAS机制实现，juc包下实现了Lock接口的类都属于乐观锁，而Synchronized关键字则是典型的悲观锁。</p>\n<h2><a id=\"_8\"></a>显式/隐式锁</h2>\n<p>隐式锁和显式锁的区别在于是否需要手动加锁和释放锁，在Java中的隐式锁为Synchronized关键字，见<br />\nhttp://www.mycookies.cn/#/blog/detail/53；而显式锁需要手动lock和unlock，java中主要表现在Lock接口的实现类，如ReentrantLock。<br />\ntodo: 代码块</p>\n<h2><a id=\"_13\"></a>公平/非公平锁</h2>\n<p>公平锁是指多个线程按照申请锁的顺序来获取锁，线程直接进入队列中排队，队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低，等待队列中除第一个线程以外的所有线程都会阻塞，CPU唤醒阻塞线程的开销比非公平锁大。<br />\nJava中的非公平锁主要体现在Synchronized关键字，而公平锁仅在ReentrantLock中才有实现，ReentrantLock支持公平锁和非公平锁两种模式。<br />\ntodo: 图</p>\n<h2><a id=\"_17\"></a>可重入/不可冲入锁</h2>\n<p>可重入锁又名递归锁，是指在同一个线程在外层方法获取锁的时候，再进入该线程的内层方法会自动获取锁（前提锁对象得是同一个对象或者class），不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁，可重入锁的一个优点是可一定程度避免死锁。<br />\n当然也可以通过代码将锁设置为不可重入。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Lock</span> </span>{\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">boolean</span> isLocked = <span class=\"hljs-keyword\">false</span>;\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">synchronized</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">lock</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span> InterruptedException</span>{\n        <span class=\"hljs-keyword\">while</span>(isLocked){\n            wait();\n        }\n        isLocked = <span class=\"hljs-keyword\">true</span>;\n    }\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">synchronized</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">unlock</span><span class=\"hljs-params\">()</span></span>{\n        isLocked = <span class=\"hljs-keyword\">false</span>;\n        notifyAll();\n    }\n}\n\n</code></div></pre>\n<h2><a id=\"_38\"></a>共享锁/排他锁</h2>\n<p>共享锁：多个线程可以同时对同一资源加共享锁，获取共享锁后只能对数据进行读取操作。<br />\n排他锁：同一时间只能由一个线程对资源加排他锁，能读锁定资源进行读写操作。<br />\n共享锁和排他锁在java中主要表现在ReadWriteLock，只有ReadLock时共享锁，其他类型的锁都是排他锁。</p>\n', '[{\"lev\":2,\"text\":\"乐观/悲观锁\",\"id\":\"_2\"},{\"lev\":2,\"text\":\"显式/隐式锁\",\"id\":\"_8\"},{\"lev\":2,\"text\":\"公平/非公平锁\",\"id\":\"_13\"},{\"lev\":2,\"text\":\"可重入/不可冲入锁\",\"id\":\"_17\"},{\"lev\":2,\"text\":\"共享锁/排他锁\",\"id\":\"_38\"}]', 'Jann Lee', 0, 184, 15, 0, 1572794252186, 1572794252194, 1);
INSERT INTO `blog` VALUES (55, 14, '万能并发框架-AQS', 'AQS是并发编程中非常重要的概念，它是juc包下的许多并发工具类，如CountdownLatch，CyclicBarrier，Semaphore 和锁, 如ReentrantLock， ReaderWriterLock的实现基础，提供了一个基于int状态码和队列来实现的并发框架', '', 'AQS是并发编程中非常重要的概念，它是juc包下的许多并发工具类，如CountdownLatch，CyclicBarrier，Semaphore 和锁, 如ReentrantLock， ReaderWriterLock的实现基础，提供了一个基于int状态码和队列来实现的并发框架。本文将对AQS框架的几个重要组成进行简要介绍，读完本文你将get到以下几个点：\n\n1. AQS进行并发控制的机制是什么\n\n2. AQS独占和共享模式是如何实现的\n\n3. 同步队列和条件等待队列的区别，和数据出入队原则\n\n    \n\n## 一，AQS基本概念\n\n AQS（AbstractQueuedSynchronizer）是用来构建锁或者其他同步组件的基础框架，它使用了一个int成员变量来表示状态，通过内置的FIFO（first in，first out）队列来完成资源获取线程的排队工作。\n\n队列可分为两种，一种是同步队列，是程序执行入口出处的等待队列；而另一种则是条件等待队列，队列中的元素是在程序执行时在某个条件上发生等待。\n\n ![](http://source.mycookies.cn/201911200753_906.png?ERROR)\n\n### 1.1 独占or共享模式\n\n AQS支持两种获取同步状态的模式既独占式和共享式。顾名思义，独占式模式同一时刻只允许一个线程获取同步状态，而共享模式则允许多个线程同时获取。\n\n ![](http://source.mycookies.cn/201911200753_235.jpg?ERROR)\n\n### 1.2 同步队列\n\n当一个线程尝试获取同步状态失败时，同步器会将这个线程以及等待状态等信息构造成一个节点加入到等待队列中，同时会阻塞当前线程，当同步状态释放时，会把首节点中的线程唤醒，使其再次尝试重复获取同步队列。\n\n ![](http://source.mycookies.cn/201911200753_670.png?ERROR)\n\n### 1.3 条件队列\n\nAQS内部类ConditionObject来实现的条件队列，当一个线程获取到同步状态，但是却通过Condition调用了await相关的方法时，会将该线程封装成Node节点并加入到条件队列中，它的结构和同步队列相同。\n\n\n\n## 二，独占or共享模式\n\nAQS框架中，通过维护一个int类型的状态，来进行并发控制，线程通常通过修改此状态信息来表明当前线程持有此同步状态。AQS则是通过保存修改状态线程的引用来实现独占和共享模式的。\n\n```java\n/**\n * 获取同步状态\n */\npublic final void acquire(int arg) {\n    //尝试获取同步状态， 如果尝试获取到同步状态失败，则加入到同步队列中\n    if (!tryAcquire(arg) &&\n        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n        selfInterrupt();\n}\n/**\n * 尝试获取同步状态【子类中实现】，因为aqs基于模板模式，仅提供基于状态和同步队列的实 \n * 现思路，具体的实现由子类决定\n */\nprotected final boolean tryAcquire(int acquires) {\n    final Thread current = Thread.currentThread();\n    int c = getState();\n    if (c == 0) {\n        // 如果当前状态值为0，并且等待队列中没有元素，执行修改状态值操作\n        if (!hasQueuedPredecessors() &&\n            compareAndSetState(0, acquires)) {\n            // 修改状态值成功，记录当前持有同步状态的线程信息\n            setExclusiveOwnerThread(current);\n            return true;\n        }\n        // 如果当前线程已经持有同步状态，继续修改同步状态【重入锁实现原理】\n    } else if (current == getExclusiveOwnerThread()) {\n        int nextc = c + acquires;\n        if (nextc < 0)\n            throw new Error(\"Maximum lock count exceeded\");\n        setState(nextc);\n        return true;\n    }\n    return false;\n}\n\n/**\n * 根据传入的模式以及当前线程信息创建一个队列的节点并加入到同步队列尾部\n */\nprivate Node addWaiter(Node mode) {\n    Node node = new Node(Thread.currentThread(), mode);\n    // Try the fast path of enq; backup to full enq on failure\n    Node pred = tail;\n    if (pred != null) {\n        node.prev = pred;\n        if (compareAndSetTail(pred, node)) {\n            pred.next = node;\n            return node;\n        }\n    }\n    enq(node);\n    return node;\n}\n/**\n * 同步队列中节点，尝试获取同步状态\n */\nfinal boolean acquireQueued(final Node node, int arg) {\n    boolean failed = true;\n    try {\n        boolean interrupted = false;\n        // 自旋(死循环)\n        for (;;) {\n		    // 只有当前节点的前驱节点是头节点时才会尝试执行获取同步状态操作\n            final Node p = node.predecessor();\n            if (p == head && tryAcquire(arg)) {\n                setHead(node);\n                p.next = null; // help GC\n                failed = false;\n                return interrupted;\n            }\n            if (shouldParkAfterFailedAcquire(p, node) &&\n                parkAndCheckInterrupt())\n                interrupted = true;\n        }\n    } finally {\n        if (failed)\n            cancelAcquire(node);\n    }\n}\n```\n\n独占式是如何控制得？\n\n当修改状态信息成功后，如果执行的是独占式操作，AQS的具体实现类中会保存当前线程的信息来声明同步状态已被当前线程占用，此时其他线程再尝试获取同步状态会返回false。\n\n## 三，同步队列\n\n### 3.1 队列中保存那些信息？\n\n同步队列节点中主要保存着线程的信息以及模式(共享or独占)。\n\n### 3.2 何时执行入队操作？\n\n```java\n/**\n * 获取同步状态\n */\npublic final void acquire(int arg) {\n    //尝试获取同步状态， 如果尝试获取到同步状态失败，则加入到同步队列中\n    if (!tryAcquire(arg) &&\n        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n        selfInterrupt();\n}\n```\n\n复用上文中的代码，不难看出再获取同步状态失败后，会执行入队操作。 \n\n![](http://source.mycookies.cn/201911200753_881.png?ERROR)\n\n### 3.3 何时执行出队操作？\n\n 当线程获取同步状态失败时，会被封装成Node节点加入到等待队列中，此时所有节点都回进入自旋过程，首先判断自己prev是否时头节点，如果是则尝试获取同步状态。\n 被阻塞线程的唤醒主要以靠前驱节点的出队或阻塞线程被中断来实现。\n\n```java\n/**\n * 同步队列中节点，尝试获取同步状态\n * \n * 1. 当一个线程获取到同步状态时，会将当前线程构造程Node并设置为头节点\n * 2. 并将原始的head节点设置为null，以便于垃圾回收\n */\nfinal boolean acquireQueued(final Node node, int arg) {\n    boolean failed = true;\n    try {\n        boolean interrupted = false;\n        for (;;) {\n            final Node p = node.predecessor();\n            if (p == head && tryAcquire(arg)) {\n                setHead(node);\n                p.next = null; // help GC\n                failed = false;\n                return interrupted;\n            }\n            if (shouldParkAfterFailedAcquire(p, node) &&\n                parkAndCheckInterrupt())\n                interrupted = true;\n        }\n    } finally {\n        if (failed)\n            cancelAcquire(node);\n    }\n}\n```\n\n## 四，条件等待队列\n\n 条件变量（ConidtionObject）是AQS中的一个内部类，用来实现同步队列机制。同步队列复用了等待队列中Node节点，所以同步队列到等待队列中不需要进行额外的转换。\n\n### 4.1 什么时候执行入队操作？\n\n当线程获取到同步状态，但是在临界区中调用了await()方法，此时该线程会被加入到对应的条件队列汇总。\n ps: 临界区，加锁和释放锁之间的代码区域\n\n```java\n/**\n * ConditionObject中的await方法，调用后使得当前执行线程加入条件等待队列\n */\npublic final void await() throws InterruptedException {\n    if (Thread.interrupted())\n        throw new InterruptedException();\n    Node node = addConditionWaiter();\n    // -----省略代码------\n}\n/**\n * 添加等待线程\n */\nprivate Node addConditionWaiter() {\n    Node t = lastWaiter;\n    // -----省略代码------\n    // 将当前线程构造程条件队列节点，并加入到队列中\n    Node node = new Node(Thread.currentThread(), Node.CONDITION);\n    if (t == null)\n        firstWaiter = node;\n    else\n        t.nextWaiter = node;\n    lastWaiter = node;\n    return node;\n}\n```\n\n### 4.2 什么时候执行出队操作？\n\n当对应的Conditioni调用signial/signalAll()方法时回选择从条件队列中出队列，同步队列是通过自旋的方式获取同步状态，而条件队列中的节点则通过通知的方式出队。条件队列中的节点被唤醒后会加入到入口等待队列中。\n\n```java\n/**\n * 唤醒当前条件等到队列中的所有等待线程\n */\npublic final void signalAll() {\n    if (!isHeldExclusively())\n        throw new IllegalMonitorStateException();\n    Node first = firstWaiter;\n    if (first != null)\n        doSignalAll(first);\n}\n/**\n * 遍历队列，将元素从条件队列 加入到 同步队列\n */\nprivate void doSignalAll(Node first) {\n    lastWaiter = firstWaiter = null;\n    do {\n        Node next = first.nextWaiter;\n        first.nextWaiter = null;\n        transferForSignal(first);\n        first = next;\n    } while (first != null);\n}\nfinal boolean transferForSignal(Node node) {\n	// -----省略代码------\n    // 执行入队操作，将node添加到同步队列中\n    Node p = enq(node);\n    int ws = p.waitStatus;\n    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))\n        LockSupport.unpark(node.thread);\n    return true;\n}\n```\n\n## 五，总结\n\n1. 使用Node实现的FIFO队列，可以用于构建锁或者其他同步装置的基础框架\n2. 利用一个int类型的属性表示状态\n3. 使用模板方法模式，子类可以通过继承它来管理状态实现各种并发工具\n4. 可以同时实现独占和共享模式\n\n本文对AQS的基本原理进行的简要的描述，对于子类的公平性和非公平行实现，中断，队列中节点的等待状态，cas等操作没有进行探讨，感兴趣的小伙伴可以进行源码阅读或者查阅相关资料。\n\n## 六，Q&A\n\nQuestion1： 在java中通常使用synchronized来实现方法同步，AQS中通过CAS保证了修改同步状态的一致性问题，那么对比synchronized，cas有什么优势不同与优势呢？你还知道其他无锁并发的策略吗？\n\n\n\n', '<p>AQS是并发编程中非常重要的概念，它是juc包下的许多并发工具类，如CountdownLatch，CyclicBarrier，Semaphore 和锁, 如ReentrantLock， ReaderWriterLock的实现基础，提供了一个基于int状态码和队列来实现的并发框架。本文将对AQS框架的几个重要组成进行简要介绍，读完本文你将get到以下几个点：</p>\n<ol>\n<li>\n<p>AQS进行并发控制的机制是什么</p>\n</li>\n<li>\n<p>AQS独占和共享模式是如何实现的</p>\n</li>\n<li>\n<p>同步队列和条件等待队列的区别，和数据出入队原则</p>\n</li>\n</ol>\n<h2><a id=\"AQS_10\"></a>一，AQS基本概念</h2>\n<p>AQS（AbstractQueuedSynchronizer）是用来构建锁或者其他同步组件的基础框架，它使用了一个int成员变量来表示状态，通过内置的FIFO（first in，first out）队列来完成资源获取线程的排队工作。</p>\n<p>队列可分为两种，一种是同步队列，是程序执行入口出处的等待队列；而另一种则是条件等待队列，队列中的元素是在程序执行时在某个条件上发生等待。</p>\n<p><img src=\"http://source.mycookies.cn/201911200753_906.png?ERROR\" alt=\"\" /></p>\n<h3><a id=\"11_or_18\"></a>1.1 独占or共享模式</h3>\n<p>AQS支持两种获取同步状态的模式既独占式和共享式。顾名思义，独占式模式同一时刻只允许一个线程获取同步状态，而共享模式则允许多个线程同时获取。</p>\n<p><img src=\"http://source.mycookies.cn/201911200753_235.jpg?ERROR\" alt=\"\" /></p>\n<h3><a id=\"12__24\"></a>1.2 同步队列</h3>\n<p>当一个线程尝试获取同步状态失败时，同步器会将这个线程以及等待状态等信息构造成一个节点加入到等待队列中，同时会阻塞当前线程，当同步状态释放时，会把首节点中的线程唤醒，使其再次尝试重复获取同步队列。</p>\n<p><img src=\"http://source.mycookies.cn/201911200753_670.png?ERROR\" alt=\"\" /></p>\n<h3><a id=\"13__30\"></a>1.3 条件队列</h3>\n<p>AQS内部类ConditionObject来实现的条件队列，当一个线程获取到同步状态，但是却通过Condition调用了await相关的方法时，会将该线程封装成Node节点并加入到条件队列中，它的结构和同步队列相同。</p>\n<h2><a id=\"or_36\"></a>二，独占or共享模式</h2>\n<p>AQS框架中，通过维护一个int类型的状态，来进行并发控制，线程通常通过修改此状态信息来表明当前线程持有此同步状态。AQS则是通过保存修改状态线程的引用来实现独占和共享模式的。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 获取同步状态\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">acquire</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> arg)</span> </span>{\n    <span class=\"hljs-comment\">//尝试获取同步状态， 如果尝试获取到同步状态失败，则加入到同步队列中</span>\n    <span class=\"hljs-keyword\">if</span> (!tryAcquire(arg) &amp;&amp;\n        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n        selfInterrupt();\n}\n<span class=\"hljs-comment\">/**\n * 尝试获取同步状态【子类中实现】，因为aqs基于模板模式，仅提供基于状态和同步队列的实 \n * 现思路，具体的实现由子类决定\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">protected</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">tryAcquire</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> acquires)</span> </span>{\n    <span class=\"hljs-keyword\">final</span> Thread current = Thread.currentThread();\n    <span class=\"hljs-keyword\">int</span> c = getState();\n    <span class=\"hljs-keyword\">if</span> (c == <span class=\"hljs-number\">0</span>) {\n        <span class=\"hljs-comment\">// 如果当前状态值为0，并且等待队列中没有元素，执行修改状态值操作</span>\n        <span class=\"hljs-keyword\">if</span> (!hasQueuedPredecessors() &amp;&amp;\n            compareAndSetState(<span class=\"hljs-number\">0</span>, acquires)) {\n            <span class=\"hljs-comment\">// 修改状态值成功，记录当前持有同步状态的线程信息</span>\n            setExclusiveOwnerThread(current);\n            <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">true</span>;\n        }\n        <span class=\"hljs-comment\">// 如果当前线程已经持有同步状态，继续修改同步状态【重入锁实现原理】</span>\n    } <span class=\"hljs-keyword\">else</span> <span class=\"hljs-keyword\">if</span> (current == getExclusiveOwnerThread()) {\n        <span class=\"hljs-keyword\">int</span> nextc = c + acquires;\n        <span class=\"hljs-keyword\">if</span> (nextc &lt; <span class=\"hljs-number\">0</span>)\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> Error(<span class=\"hljs-string\">\"Maximum lock count exceeded\"</span>);\n        setState(nextc);\n        <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">true</span>;\n    }\n    <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">false</span>;\n}\n\n<span class=\"hljs-comment\">/**\n * 根据传入的模式以及当前线程信息创建一个队列的节点并加入到同步队列尾部\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> Node <span class=\"hljs-title\">addWaiter</span><span class=\"hljs-params\">(Node mode)</span> </span>{\n    Node node = <span class=\"hljs-keyword\">new</span> Node(Thread.currentThread(), mode);\n    <span class=\"hljs-comment\">// Try the fast path of enq; backup to full enq on failure</span>\n    Node pred = tail;\n    <span class=\"hljs-keyword\">if</span> (pred != <span class=\"hljs-keyword\">null</span>) {\n        node.prev = pred;\n        <span class=\"hljs-keyword\">if</span> (compareAndSetTail(pred, node)) {\n            pred.next = node;\n            <span class=\"hljs-keyword\">return</span> node;\n        }\n    }\n    enq(node);\n    <span class=\"hljs-keyword\">return</span> node;\n}\n<span class=\"hljs-comment\">/**\n * 同步队列中节点，尝试获取同步状态\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">acquireQueued</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">final</span> Node node, <span class=\"hljs-keyword\">int</span> arg)</span> </span>{\n    <span class=\"hljs-keyword\">boolean</span> failed = <span class=\"hljs-keyword\">true</span>;\n    <span class=\"hljs-keyword\">try</span> {\n        <span class=\"hljs-keyword\">boolean</span> interrupted = <span class=\"hljs-keyword\">false</span>;\n        <span class=\"hljs-comment\">// 自旋(死循环)</span>\n        <span class=\"hljs-keyword\">for</span> (;;) {\n		    <span class=\"hljs-comment\">// 只有当前节点的前驱节点是头节点时才会尝试执行获取同步状态操作</span>\n            <span class=\"hljs-keyword\">final</span> Node p = node.predecessor();\n            <span class=\"hljs-keyword\">if</span> (p == head &amp;&amp; tryAcquire(arg)) {\n                setHead(node);\n                p.next = <span class=\"hljs-keyword\">null</span>; <span class=\"hljs-comment\">// help GC</span>\n                failed = <span class=\"hljs-keyword\">false</span>;\n                <span class=\"hljs-keyword\">return</span> interrupted;\n            }\n            <span class=\"hljs-keyword\">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;\n                parkAndCheckInterrupt())\n                interrupted = <span class=\"hljs-keyword\">true</span>;\n        }\n    } <span class=\"hljs-keyword\">finally</span> {\n        <span class=\"hljs-keyword\">if</span> (failed)\n            cancelAcquire(node);\n    }\n}\n</code></div></pre>\n<p>独占式是如何控制得？</p>\n<p>当修改状态信息成功后，如果执行的是独占式操作，AQS的具体实现类中会保存当前线程的信息来声明同步状态已被当前线程占用，此时其他线程再尝试获取同步状态会返回false。</p>\n<h2><a id=\"_125\"></a>三，同步队列</h2>\n<h3><a id=\"31__127\"></a>3.1 队列中保存那些信息？</h3>\n<p>同步队列节点中主要保存着线程的信息以及模式(共享or独占)。</p>\n<h3><a id=\"32__131\"></a>3.2 何时执行入队操作？</h3>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 获取同步状态\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">acquire</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int</span> arg)</span> </span>{\n    <span class=\"hljs-comment\">//尝试获取同步状态， 如果尝试获取到同步状态失败，则加入到同步队列中</span>\n    <span class=\"hljs-keyword\">if</span> (!tryAcquire(arg) &amp;&amp;\n        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))\n        selfInterrupt();\n}\n</code></div></pre>\n<p>复用上文中的代码，不难看出再获取同步状态失败后，会执行入队操作。</p>\n<p><img src=\"http://source.mycookies.cn/201911200753_881.png?ERROR\" alt=\"\" /></p>\n<h3><a id=\"33__149\"></a>3.3 何时执行出队操作？</h3>\n<p>当线程获取同步状态失败时，会被封装成Node节点加入到等待队列中，此时所有节点都回进入自旋过程，首先判断自己prev是否时头节点，如果是则尝试获取同步状态。<br />\n被阻塞线程的唤醒主要以靠前驱节点的出队或阻塞线程被中断来实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 同步队列中节点，尝试获取同步状态\n * \n * 1. 当一个线程获取到同步状态时，会将当前线程构造程Node并设置为头节点\n * 2. 并将原始的head节点设置为null，以便于垃圾回收\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">acquireQueued</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">final</span> Node node, <span class=\"hljs-keyword\">int</span> arg)</span> </span>{\n    <span class=\"hljs-keyword\">boolean</span> failed = <span class=\"hljs-keyword\">true</span>;\n    <span class=\"hljs-keyword\">try</span> {\n        <span class=\"hljs-keyword\">boolean</span> interrupted = <span class=\"hljs-keyword\">false</span>;\n        <span class=\"hljs-keyword\">for</span> (;;) {\n            <span class=\"hljs-keyword\">final</span> Node p = node.predecessor();\n            <span class=\"hljs-keyword\">if</span> (p == head &amp;&amp; tryAcquire(arg)) {\n                setHead(node);\n                p.next = <span class=\"hljs-keyword\">null</span>; <span class=\"hljs-comment\">// help GC</span>\n                failed = <span class=\"hljs-keyword\">false</span>;\n                <span class=\"hljs-keyword\">return</span> interrupted;\n            }\n            <span class=\"hljs-keyword\">if</span> (shouldParkAfterFailedAcquire(p, node) &amp;&amp;\n                parkAndCheckInterrupt())\n                interrupted = <span class=\"hljs-keyword\">true</span>;\n        }\n    } <span class=\"hljs-keyword\">finally</span> {\n        <span class=\"hljs-keyword\">if</span> (failed)\n            cancelAcquire(node);\n    }\n}\n</code></div></pre>\n<h2><a id=\"_184\"></a>四，条件等待队列</h2>\n<p>条件变量（ConidtionObject）是AQS中的一个内部类，用来实现同步队列机制。同步队列复用了等待队列中Node节点，所以同步队列到等待队列中不需要进行额外的转换。</p>\n<h3><a id=\"41__188\"></a>4.1 什么时候执行入队操作？</h3>\n<p>当线程获取到同步状态，但是在临界区中调用了await()方法，此时该线程会被加入到对应的条件队列汇总。<br />\nps: 临界区，加锁和释放锁之间的代码区域</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * ConditionObject中的await方法，调用后使得当前执行线程加入条件等待队列\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">await</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n    <span class=\"hljs-keyword\">if</span> (Thread.interrupted())\n        <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> InterruptedException();\n    Node node = addConditionWaiter();\n    <span class=\"hljs-comment\">// -----省略代码------</span>\n}\n<span class=\"hljs-comment\">/**\n * 添加等待线程\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> Node <span class=\"hljs-title\">addConditionWaiter</span><span class=\"hljs-params\">()</span> </span>{\n    Node t = lastWaiter;\n    <span class=\"hljs-comment\">// -----省略代码------</span>\n    <span class=\"hljs-comment\">// 将当前线程构造程条件队列节点，并加入到队列中</span>\n    Node node = <span class=\"hljs-keyword\">new</span> Node(Thread.currentThread(), Node.CONDITION);\n    <span class=\"hljs-keyword\">if</span> (t == <span class=\"hljs-keyword\">null</span>)\n        firstWaiter = node;\n    <span class=\"hljs-keyword\">else</span>\n        t.nextWaiter = node;\n    lastWaiter = node;\n    <span class=\"hljs-keyword\">return</span> node;\n}\n</code></div></pre>\n<h3><a id=\"42__220\"></a>4.2 什么时候执行出队操作？</h3>\n<p>当对应的Conditioni调用signial/signalAll()方法时回选择从条件队列中出队列，同步队列是通过自旋的方式获取同步状态，而条件队列中的节点则通过通知的方式出队。条件队列中的节点被唤醒后会加入到入口等待队列中。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 唤醒当前条件等到队列中的所有等待线程\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">signalAll</span><span class=\"hljs-params\">()</span> </span>{\n    <span class=\"hljs-keyword\">if</span> (!isHeldExclusively())\n        <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> IllegalMonitorStateException();\n    Node first = firstWaiter;\n    <span class=\"hljs-keyword\">if</span> (first != <span class=\"hljs-keyword\">null</span>)\n        doSignalAll(first);\n}\n<span class=\"hljs-comment\">/**\n * 遍历队列，将元素从条件队列 加入到 同步队列\n */</span>\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">doSignalAll</span><span class=\"hljs-params\">(Node first)</span> </span>{\n    lastWaiter = firstWaiter = <span class=\"hljs-keyword\">null</span>;\n    <span class=\"hljs-keyword\">do</span> {\n        Node next = first.nextWaiter;\n        first.nextWaiter = <span class=\"hljs-keyword\">null</span>;\n        transferForSignal(first);\n        first = next;\n    } <span class=\"hljs-keyword\">while</span> (first != <span class=\"hljs-keyword\">null</span>);\n}\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">transferForSignal</span><span class=\"hljs-params\">(Node node)</span> </span>{\n	<span class=\"hljs-comment\">// -----省略代码------</span>\n    <span class=\"hljs-comment\">// 执行入队操作，将node添加到同步队列中</span>\n    Node p = enq(node);\n    <span class=\"hljs-keyword\">int</span> ws = p.waitStatus;\n    <span class=\"hljs-keyword\">if</span> (ws &gt; <span class=\"hljs-number\">0</span> || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))\n        LockSupport.unpark(node.thread);\n    <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">true</span>;\n}\n</code></div></pre>\n<h2><a id=\"_258\"></a>五，总结</h2>\n<ol>\n<li>使用Node实现的FIFO队列，可以用于构建锁或者其他同步装置的基础框架</li>\n<li>利用一个int类型的属性表示状态</li>\n<li>使用模板方法模式，子类可以通过继承它来管理状态实现各种并发工具</li>\n<li>可以同时实现独占和共享模式</li>\n</ol>\n<p>本文对AQS的基本原理进行的简要的描述，对于子类的公平性和非公平行实现，中断，队列中节点的等待状态，cas等操作没有进行探讨，感兴趣的小伙伴可以进行源码阅读或者查阅相关资料。</p>\n<h2><a id=\"QA_267\"></a>六，Q&amp;A</h2>\n<p>Question1： 在java中通常使用synchronized来实现方法同步，AQS中通过CAS保证了修改同步状态的一致性问题，那么对比synchronized，cas有什么优势不同与优势呢？你还知道其他无锁并发的策略吗？</p>\n', '[{\"lev\":2,\"text\":\"一，AQS基本概念\",\"id\":\"AQS_10\"},{\"lev\":3,\"text\":\"1.1 独占or共享模式\",\"id\":\"11_or_18\"},{\"lev\":3,\"text\":\"1.2 同步队列\",\"id\":\"12__24\"},{\"lev\":3,\"text\":\"1.3 条件队列\",\"id\":\"13__30\"},{\"lev\":2,\"text\":\"二，独占or共享模式\",\"id\":\"or_36\"},{\"lev\":2,\"text\":\"三，同步队列\",\"id\":\"_125\"},{\"lev\":3,\"text\":\"3.1 队列中保存那些信息？\",\"id\":\"31__127\"},{\"lev\":3,\"text\":\"3.2 何时执行入队操作？\",\"id\":\"32__131\"},{\"lev\":3,\"text\":\"3.3 何时执行出队操作？\",\"id\":\"33__149\"},{\"lev\":2,\"text\":\"四，条件等待队列\",\"id\":\"_184\"},{\"lev\":3,\"text\":\"4.1 什么时候执行入队操作？\",\"id\":\"41__188\"},{\"lev\":3,\"text\":\"4.2 什么时候执行出队操作？\",\"id\":\"42__220\"},{\"lev\":2,\"text\":\"五，总结\",\"id\":\"_258\"},{\"lev\":2,\"text\":\"六，Q&A\",\"id\":\"QA_267\"}]', 'Jann Lee', 0, 346, 10, 0, 1574207740274, 1574207740401, 1);
INSERT INTO `blog` VALUES (56, 14, '无锁并发-CAS', '浅谈CAS实现原理，对比CAS与加锁的性能差异，A', '', '### Talk is cheap\n\nCAS(Compare And Swap)，即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制，CAS操作包含三个操作数——内存位置（V）、预期原值（A）和新值(B)。如果内存位置的值与预期原值相匹配，那么处理器会自动将该位置值更新为新值。否则，处理器不做任何操作。无论位置V的值是否等于A， 都将返回V原有的值。\n\nCAS的含义是”我认为V的值应该是A，如果是，那我将V的值更新为B，否则不修改并告诉V的值实际是多少“\n\n### Show you my code\n\n在单线程环境中分别使用无锁，加锁以及cas进行十组5亿次累加运算，然后打印出平均耗时。\n\n```java\n /**\n * cas对比加锁测试\n *\n * @author Jann Lee\n * @date 2019-11-21 0:12\n **/\npublic class CasTest {\n\n    @Test\n    public void test() {\n        long times = 500_000_000;\n        // 记录耗时\n        List<Long> elapsedTime4NoLock = new ArrayList<>(10);\n        List<Long> elapsedTime4Synchronized = new ArrayList<>(10);\n        List<Long> elapsedTime4ReentrantLock = new ArrayList<>(10);\n        List<Long> elapsedTime4Cas = new ArrayList<>(10);\n\n        // 进行10组试验\n        for (int j = 0; j < 10; j++) {\n            // 无锁\n            long startTime = System.currentTimeMillis();\n            for (long i = 0; i < times; i++) {\n            }\n            long endTime = System.currentTimeMillis();\n            elapsedTime4NoLock.add(endTime - startTime);\n\n            // synchronized 关键字（隐式锁）\n            startTime = endTime;\n            for (long i = 0; i < times; ) {\n                i = addWithSynchronized(i);\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4Synchronized.add(endTime - startTime);\n\n            // ReentrantLock 显式锁\n            startTime = endTime;\n            ReentrantLock lock = new ReentrantLock();\n            for (long i = 0; i < times; ) {\n                i = addWithReentrantLock(i, lock);\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4ReentrantLock.add(endTime - startTime);\n\n            // cas(AtomicLong底层是用cas实现)\n            startTime = endTime;\n            AtomicLong atomicLong = new AtomicLong();\n            while (atomicLong.getAndIncrement() < times) {\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4Cas.add(endTime - startTime);\n        }\n\n        System.out.println(\"无锁计算耗时: \" + average(elapsedTime4NoLock) + \"ms\");\n        System.out.println(\"synchronized计算耗时: \" + average(elapsedTime4Synchronized) + \"ms\");\n        System.out.println(\"ReentrantLock计算耗时: \" + average(elapsedTime4ReentrantLock) + \"ms\");\n        System.out.println(\"cas计算耗时: \" + average(elapsedTime4Cas) + \"ms\");\n\n    }\n\n    /**\n     * synchronized加锁\n     */\n    private synchronized long addWithSynchronized(long i) {\n        i = i + 1;\n        return i;\n    }\n\n    /**\n     * ReentrantLock加锁\n     */\n    private long addWithReentrantLock(long i, Lock lock) {\n        lock.lock();\n        i = i + 1;\n        lock.unlock();\n        return i;\n    }\n\n    /**\n     * 计算平均耗时\n     */\n    private double average(Collection<Long> collection) {\n        return collection.stream().mapToLong(i -> i).average().orElse(0);\n    }\n}\n\n```\n\n ![](http://source.mycookies.cn/201911210054_266.png?ERROR)\n\n从案例中我们可能看出在单线程环境场景下cas的性能要高于锁相关的操作。当然，在竞争比较激烈的情况下性能可能会有所下降，因为**要不断的重试和回退或者放弃操作**，这也是CAS的一个缺点所在，因为这些重试，回退等操作通常用开发者来实现。\n\nCAS的实现并非是简单的代码层面控制的，而是需要硬件的支持，因此在不同的体系架构之间执行的性能差异很大。但是一个很管用的经验法则是：在大多数处理器上，在无竞争的锁获取和释放的”快速代码路径“上的开销，大约是CAS开销的两倍。\n\n### 为何CAS如此优秀\n\n**硬件加持**，现代大多数处理器都从硬件层面通过一些列指令实现CompareAndSwap(比较并交换)同步原语，进而使操作系统和JVM可以直接使用这些指令实现锁和并发的数据结构。我们可以简单认为，**CAS是将比较和交换合成是一个原子操作**。\n\n**JVM对CAS的支持**， 由于Java程序运行在JVM上，所以应对不同的硬件体系架构的处理则需要JVM来实现。在不支持CAS操作的硬件上，jvm将使用自旋锁来实现。\n\n### CAS的ABA问题\n\ncas操作让我们减少了锁带来的性能损耗，同时也给我们带来了新的麻烦-ABA问题。 \n\n![](http://source.mycookies.cn/201911210000_918.png?ERROR)\n\n在线程A读取到x的值与执行CAS操作期间，线程B对x执行了两次修改，x的值从100变成200，然后再从200变回100；而后在线程A执行CAS操作过程中并未发现x发生过变化，成功修改了x的值。由于x的值100 ->200->100,所以称之为ABA的原因。\n\n魔高一尺道高一丈，解决ABA的问题目前最常用的办法就是给数据加上“版本号”，每次修改数据时同时改变版本号即可。\n\n![](http://source.mycookies.cn/201911210009_137.png?ERROR)\n\n### Q&A\n\n在竞争比较激烈的情况下，CAS要进行回退，重试等操作才能得到正确的结果，那么CAS一定比加锁性能要高吗？', '<h3><a id=\"Talk_is_cheap_0\"></a>Talk is cheap</h3>\n<p>CAS(Compare And Swap)，即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制，CAS操作包含三个操作数——内存位置（V）、预期原值（A）和新值(B)。如果内存位置的值与预期原值相匹配，那么处理器会自动将该位置值更新为新值。否则，处理器不做任何操作。无论位置V的值是否等于A， 都将返回V原有的值。</p>\n<p>CAS的含义是”我认为V的值应该是A，如果是，那我将V的值更新为B，否则不修改并告诉V的值实际是多少“</p>\n<h3><a id=\"Show_you_my_code_6\"></a>Show you my code</h3>\n<p>在单线程环境中分别使用无锁，加锁以及cas进行十组5亿次累加运算，然后打印出平均耗时。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"> <span class=\"hljs-comment\">/**\n * cas对比加锁测试\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-11-21 0:12\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">CasTest</span> </span>{\n\n    <span class=\"hljs-meta\">@Test</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">test</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">long</span> times = <span class=\"hljs-number\">500_000_000</span>;\n        <span class=\"hljs-comment\">// 记录耗时</span>\n        List&lt;Long&gt; elapsedTime4NoLock = <span class=\"hljs-keyword\">new</span> ArrayList&lt;&gt;(<span class=\"hljs-number\">10</span>);\n        List&lt;Long&gt; elapsedTime4Synchronized = <span class=\"hljs-keyword\">new</span> ArrayList&lt;&gt;(<span class=\"hljs-number\">10</span>);\n        List&lt;Long&gt; elapsedTime4ReentrantLock = <span class=\"hljs-keyword\">new</span> ArrayList&lt;&gt;(<span class=\"hljs-number\">10</span>);\n        List&lt;Long&gt; elapsedTime4Cas = <span class=\"hljs-keyword\">new</span> ArrayList&lt;&gt;(<span class=\"hljs-number\">10</span>);\n\n        <span class=\"hljs-comment\">// 进行10组试验</span>\n        <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">int</span> j = <span class=\"hljs-number\">0</span>; j &lt; <span class=\"hljs-number\">10</span>; j++) {\n            <span class=\"hljs-comment\">// 无锁</span>\n            <span class=\"hljs-keyword\">long</span> startTime = System.currentTimeMillis();\n            <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">long</span> i = <span class=\"hljs-number\">0</span>; i &lt; times; i++) {\n            }\n            <span class=\"hljs-keyword\">long</span> endTime = System.currentTimeMillis();\n            elapsedTime4NoLock.add(endTime - startTime);\n\n            <span class=\"hljs-comment\">// synchronized 关键字（隐式锁）</span>\n            startTime = endTime;\n            <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">long</span> i = <span class=\"hljs-number\">0</span>; i &lt; times; ) {\n                i = addWithSynchronized(i);\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4Synchronized.add(endTime - startTime);\n\n            <span class=\"hljs-comment\">// ReentrantLock 显式锁</span>\n            startTime = endTime;\n            ReentrantLock lock = <span class=\"hljs-keyword\">new</span> ReentrantLock();\n            <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">long</span> i = <span class=\"hljs-number\">0</span>; i &lt; times; ) {\n                i = addWithReentrantLock(i, lock);\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4ReentrantLock.add(endTime - startTime);\n\n            <span class=\"hljs-comment\">// cas(AtomicLong底层是用cas实现)</span>\n            startTime = endTime;\n            AtomicLong atomicLong = <span class=\"hljs-keyword\">new</span> AtomicLong();\n            <span class=\"hljs-keyword\">while</span> (atomicLong.getAndIncrement() &lt; times) {\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4Cas.add(endTime - startTime);\n        }\n\n        System.out.println(<span class=\"hljs-string\">\"无锁计算耗时: \"</span> + average(elapsedTime4NoLock) + <span class=\"hljs-string\">\"ms\"</span>);\n        System.out.println(<span class=\"hljs-string\">\"synchronized计算耗时: \"</span> + average(elapsedTime4Synchronized) + <span class=\"hljs-string\">\"ms\"</span>);\n        System.out.println(<span class=\"hljs-string\">\"ReentrantLock计算耗时: \"</span> + average(elapsedTime4ReentrantLock) + <span class=\"hljs-string\">\"ms\"</span>);\n        System.out.println(<span class=\"hljs-string\">\"cas计算耗时: \"</span> + average(elapsedTime4Cas) + <span class=\"hljs-string\">\"ms\"</span>);\n\n    }\n\n    <span class=\"hljs-comment\">/**\n     * synchronized加锁\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">synchronized</span> <span class=\"hljs-keyword\">long</span> <span class=\"hljs-title\">addWithSynchronized</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">long</span> i)</span> </span>{\n        i = i + <span class=\"hljs-number\">1</span>;\n        <span class=\"hljs-keyword\">return</span> i;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * ReentrantLock加锁\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">long</span> <span class=\"hljs-title\">addWithReentrantLock</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">long</span> i, Lock lock)</span> </span>{\n        lock.lock();\n        i = i + <span class=\"hljs-number\">1</span>;\n        lock.unlock();\n        <span class=\"hljs-keyword\">return</span> i;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 计算平均耗时\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">double</span> <span class=\"hljs-title\">average</span><span class=\"hljs-params\">(Collection&lt;Long&gt; collection)</span> </span>{\n        <span class=\"hljs-keyword\">return</span> collection.stream().mapToLong(i -&gt; i).average().orElse(<span class=\"hljs-number\">0</span>);\n    }\n}\n\n</code></div></pre>\n<p><img src=\"http://source.mycookies.cn/201911210054_266.png?ERROR\" alt=\"\" /></p>\n<p>从案例中我们可能看出在单线程环境场景下cas的性能要高于锁相关的操作。当然，在竞争比较激烈的情况下性能可能会有所下降，因为<strong>要不断的重试和回退或者放弃操作</strong>，这也是CAS的一个缺点所在，因为这些重试，回退等操作通常用开发者来实现。</p>\n<p>CAS的实现并非是简单的代码层面控制的，而是需要硬件的支持，因此在不同的体系架构之间执行的性能差异很大。但是一个很管用的经验法则是：在大多数处理器上，在无竞争的锁获取和释放的”快速代码路径“上的开销，大约是CAS开销的两倍。</p>\n<h3><a id=\"CAS_104\"></a>为何CAS如此优秀</h3>\n<p><strong>硬件加持</strong>，现代大多数处理器都从硬件层面通过一些列指令实现CompareAndSwap(比较并交换)同步原语，进而使操作系统和JVM可以直接使用这些指令实现锁和并发的数据结构。我们可以简单认为，<strong>CAS是将比较和交换合成是一个原子操作</strong>。</p>\n<p><strong>JVM对CAS的支持</strong>， 由于Java程序运行在JVM上，所以应对不同的硬件体系架构的处理则需要JVM来实现。在不支持CAS操作的硬件上，jvm将使用自旋锁来实现。</p>\n<h3><a id=\"CASABA_110\"></a>CAS的ABA问题</h3>\n<p>cas操作让我们减少了锁带来的性能损耗，同时也给我们带来了新的麻烦-ABA问题。</p>\n<p><img src=\"http://source.mycookies.cn/201911210000_918.png?ERROR\" alt=\"\" /></p>\n<p>在线程A读取到x的值与执行CAS操作期间，线程B对x执行了两次修改，x的值从100变成200，然后再从200变回100；而后在线程A执行CAS操作过程中并未发现x发生过变化，成功修改了x的值。由于x的值100 -&gt;200-&gt;100,所以称之为ABA的原因。</p>\n<p>魔高一尺道高一丈，解决ABA的问题目前最常用的办法就是给数据加上“版本号”，每次修改数据时同时改变版本号即可。</p>\n<p><img src=\"http://source.mycookies.cn/201911210009_137.png?ERROR\" alt=\"\" /></p>\n<h3><a id=\"QA_122\"></a>Q&amp;A</h3>\n<p>在竞争比较激烈的情况下，CAS要进行回退，重试等操作才能得到正确的结果，那么CAS一定比加锁性能要高吗？</p>\n', '[{\"lev\":3,\"text\":\"Talk is cheap\",\"id\":\"Talk_is_cheap_0\"},{\"lev\":3,\"text\":\"Show you my code\",\"id\":\"Show_you_my_code_6\"},{\"lev\":3,\"text\":\"为何CAS如此优秀\",\"id\":\"CAS_104\"},{\"lev\":3,\"text\":\"CAS的ABA问题\",\"id\":\"CASABA_110\"},{\"lev\":3,\"text\":\"Q&A\",\"id\":\"QA_122\"}]', NULL, 0, 1, 1, NULL, 1574270981915, 1574271039772, 0);
INSERT INTO `blog` VALUES (57, 14, '无锁并发-CAS', '浅谈CAS实现原理，对比CAS与加锁的性能差异，分析ABA问题和解决方案', '201911210009_137.png?ERROR', '### Talk is cheap\n\nCAS(Compare And Swap)，即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制，CAS操作包含三个操作数——内存位置（V）、预期原值（A）和新值(B)。如果内存位置的值与预期原值相匹配，那么处理器会自动将该位置值更新为新值。否则，处理器不做任何操作。无论位置V的值是否等于A， 都将返回V原有的值。\n\nCAS的含义是”我认为V的值应该是A，如果是，那我将V的值更新为B，否则不修改并告诉V的值实际是多少“\n\n### Show you my code\n\n在单线程环境中分别使用无锁，加锁以及cas进行十组5亿次累加运算，然后打印出平均耗时。\n\n```java\n /**\n * cas对比加锁测试\n *\n * @author Jann Lee\n * @date 2019-11-21 0:12\n **/\npublic class CasTest {\n\n    @Test\n    public void test() {\n        long times = 500_000_000;\n        // 记录耗时\n        List<Long> elapsedTime4NoLock = new ArrayList<>(10);\n        List<Long> elapsedTime4Synchronized = new ArrayList<>(10);\n        List<Long> elapsedTime4ReentrantLock = new ArrayList<>(10);\n        List<Long> elapsedTime4Cas = new ArrayList<>(10);\n\n        // 进行10组试验\n        for (int j = 0; j < 10; j++) {\n            // 无锁\n            long startTime = System.currentTimeMillis();\n            for (long i = 0; i < times; i++) {\n            }\n            long endTime = System.currentTimeMillis();\n            elapsedTime4NoLock.add(endTime - startTime);\n\n            // synchronized 关键字（隐式锁）\n            startTime = endTime;\n            for (long i = 0; i < times; ) {\n                i = addWithSynchronized(i);\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4Synchronized.add(endTime - startTime);\n\n            // ReentrantLock 显式锁\n            startTime = endTime;\n            ReentrantLock lock = new ReentrantLock();\n            for (long i = 0; i < times; ) {\n                i = addWithReentrantLock(i, lock);\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4ReentrantLock.add(endTime - startTime);\n\n            // cas(AtomicLong底层是用cas实现)\n            startTime = endTime;\n            AtomicLong atomicLong = new AtomicLong();\n            while (atomicLong.getAndIncrement() < times) {\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4Cas.add(endTime - startTime);\n        }\n\n        System.out.println(\"无锁计算耗时: \" + average(elapsedTime4NoLock) + \"ms\");\n        System.out.println(\"synchronized计算耗时: \" + average(elapsedTime4Synchronized) + \"ms\");\n        System.out.println(\"ReentrantLock计算耗时: \" + average(elapsedTime4ReentrantLock) + \"ms\");\n        System.out.println(\"cas计算耗时: \" + average(elapsedTime4Cas) + \"ms\");\n\n    }\n\n    /**\n     * synchronized加锁\n     */\n    private synchronized long addWithSynchronized(long i) {\n        i = i + 1;\n        return i;\n    }\n\n    /**\n     * ReentrantLock加锁\n     */\n    private long addWithReentrantLock(long i, Lock lock) {\n        lock.lock();\n        i = i + 1;\n        lock.unlock();\n        return i;\n    }\n\n    /**\n     * 计算平均耗时\n     */\n    private double average(Collection<Long> collection) {\n        return collection.stream().mapToLong(i -> i).average().orElse(0);\n    }\n}\n\n```\n\n ![](http://source.mycookies.cn/201911210054_266.png?ERROR)\n\n从案例中我们可能看出在单线程环境场景下cas的性能要高于锁相关的操作。当然，在竞争比较激烈的情况下性能可能会有所下降，因为**要不断的重试和回退或者放弃操作**，这也是CAS的一个缺点所在，因为这些重试，回退等操作通常用开发者来实现。\n\nCAS的实现并非是简单的代码层面控制的，而是需要硬件的支持，因此在不同的体系架构之间执行的性能差异很大。但是一个很管用的经验法则是：在大多数处理器上，在无竞争的锁获取和释放的”快速代码路径“上的开销，大约是CAS开销的两倍。\n\n### 为何CAS如此优秀\n\n**硬件加持**，现代大多数处理器都从硬件层面通过一些列指令实现CompareAndSwap(比较并交换)同步原语，进而使操作系统和JVM可以直接使用这些指令实现锁和并发的数据结构。我们可以简单认为，**CAS是将比较和交换合成是一个原子操作**。\n\n**JVM对CAS的支持**， 由于Java程序运行在JVM上，所以应对不同的硬件体系架构的处理则需要JVM来实现。在不支持CAS操作的硬件上，jvm将使用自旋锁来实现。\n\n### CAS的ABA问题\n\ncas操作让我们减少了锁带来的性能损耗，同时也给我们带来了新的麻烦-ABA问题。 \n\n![](http://source.mycookies.cn/201911210000_918.png?ERROR)\n\n在线程A读取到x的值与执行CAS操作期间，线程B对x执行了两次修改，x的值从100变成200，然后再从200变回100；而后在线程A执行CAS操作过程中并未发现x发生过变化，成功修改了x的值。由于x的值100 ->200->100,所以称之为ABA的原因。\n\n魔高一尺道高一丈，解决ABA的问题目前最常用的办法就是给数据加上“版本号”，每次修改数据时同时改变版本号即可。\n\n![](http://source.mycookies.cn/201911210009_137.png?ERROR)\n\n### Q&A\n\n在竞争比较激烈的情况下，CAS要进行回退，重试等操作才能得到正确的结果，那么CAS一定比加锁性能要高吗？', '<h3><a id=\"Talk_is_cheap_0\"></a>Talk is cheap</h3>\n<p>CAS(Compare And Swap)，即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制，CAS操作包含三个操作数——内存位置（V）、预期原值（A）和新值(B)。如果内存位置的值与预期原值相匹配，那么处理器会自动将该位置值更新为新值。否则，处理器不做任何操作。无论位置V的值是否等于A， 都将返回V原有的值。</p>\n<p>CAS的含义是”我认为V的值应该是A，如果是，那我将V的值更新为B，否则不修改并告诉V的值实际是多少“</p>\n<h3><a id=\"Show_you_my_code_6\"></a>Show you my code</h3>\n<p>在单线程环境中分别使用无锁，加锁以及cas进行十组5亿次累加运算，然后打印出平均耗时。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"> <span class=\"hljs-comment\">/**\n * cas对比加锁测试\n *\n * <span class=\"hljs-doctag\">@author</span> Jann Lee\n * <span class=\"hljs-doctag\">@date</span> 2019-11-21 0:12\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">CasTest</span> </span>{\n\n    <span class=\"hljs-meta\">@Test</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">test</span><span class=\"hljs-params\">()</span> </span>{\n        <span class=\"hljs-keyword\">long</span> times = <span class=\"hljs-number\">500_000_000</span>;\n        <span class=\"hljs-comment\">// 记录耗时</span>\n        List&lt;Long&gt; elapsedTime4NoLock = <span class=\"hljs-keyword\">new</span> ArrayList&lt;&gt;(<span class=\"hljs-number\">10</span>);\n        List&lt;Long&gt; elapsedTime4Synchronized = <span class=\"hljs-keyword\">new</span> ArrayList&lt;&gt;(<span class=\"hljs-number\">10</span>);\n        List&lt;Long&gt; elapsedTime4ReentrantLock = <span class=\"hljs-keyword\">new</span> ArrayList&lt;&gt;(<span class=\"hljs-number\">10</span>);\n        List&lt;Long&gt; elapsedTime4Cas = <span class=\"hljs-keyword\">new</span> ArrayList&lt;&gt;(<span class=\"hljs-number\">10</span>);\n\n        <span class=\"hljs-comment\">// 进行10组试验</span>\n        <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">int</span> j = <span class=\"hljs-number\">0</span>; j &lt; <span class=\"hljs-number\">10</span>; j++) {\n            <span class=\"hljs-comment\">// 无锁</span>\n            <span class=\"hljs-keyword\">long</span> startTime = System.currentTimeMillis();\n            <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">long</span> i = <span class=\"hljs-number\">0</span>; i &lt; times; i++) {\n            }\n            <span class=\"hljs-keyword\">long</span> endTime = System.currentTimeMillis();\n            elapsedTime4NoLock.add(endTime - startTime);\n\n            <span class=\"hljs-comment\">// synchronized 关键字（隐式锁）</span>\n            startTime = endTime;\n            <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">long</span> i = <span class=\"hljs-number\">0</span>; i &lt; times; ) {\n                i = addWithSynchronized(i);\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4Synchronized.add(endTime - startTime);\n\n            <span class=\"hljs-comment\">// ReentrantLock 显式锁</span>\n            startTime = endTime;\n            ReentrantLock lock = <span class=\"hljs-keyword\">new</span> ReentrantLock();\n            <span class=\"hljs-keyword\">for</span> (<span class=\"hljs-keyword\">long</span> i = <span class=\"hljs-number\">0</span>; i &lt; times; ) {\n                i = addWithReentrantLock(i, lock);\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4ReentrantLock.add(endTime - startTime);\n\n            <span class=\"hljs-comment\">// cas(AtomicLong底层是用cas实现)</span>\n            startTime = endTime;\n            AtomicLong atomicLong = <span class=\"hljs-keyword\">new</span> AtomicLong();\n            <span class=\"hljs-keyword\">while</span> (atomicLong.getAndIncrement() &lt; times) {\n            }\n            endTime = System.currentTimeMillis();\n            elapsedTime4Cas.add(endTime - startTime);\n        }\n\n        System.out.println(<span class=\"hljs-string\">\"无锁计算耗时: \"</span> + average(elapsedTime4NoLock) + <span class=\"hljs-string\">\"ms\"</span>);\n        System.out.println(<span class=\"hljs-string\">\"synchronized计算耗时: \"</span> + average(elapsedTime4Synchronized) + <span class=\"hljs-string\">\"ms\"</span>);\n        System.out.println(<span class=\"hljs-string\">\"ReentrantLock计算耗时: \"</span> + average(elapsedTime4ReentrantLock) + <span class=\"hljs-string\">\"ms\"</span>);\n        System.out.println(<span class=\"hljs-string\">\"cas计算耗时: \"</span> + average(elapsedTime4Cas) + <span class=\"hljs-string\">\"ms\"</span>);\n\n    }\n\n    <span class=\"hljs-comment\">/**\n     * synchronized加锁\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">synchronized</span> <span class=\"hljs-keyword\">long</span> <span class=\"hljs-title\">addWithSynchronized</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">long</span> i)</span> </span>{\n        i = i + <span class=\"hljs-number\">1</span>;\n        <span class=\"hljs-keyword\">return</span> i;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * ReentrantLock加锁\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">long</span> <span class=\"hljs-title\">addWithReentrantLock</span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">long</span> i, Lock lock)</span> </span>{\n        lock.lock();\n        i = i + <span class=\"hljs-number\">1</span>;\n        lock.unlock();\n        <span class=\"hljs-keyword\">return</span> i;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 计算平均耗时\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">double</span> <span class=\"hljs-title\">average</span><span class=\"hljs-params\">(Collection&lt;Long&gt; collection)</span> </span>{\n        <span class=\"hljs-keyword\">return</span> collection.stream().mapToLong(i -&gt; i).average().orElse(<span class=\"hljs-number\">0</span>);\n    }\n}\n\n</code></div></pre>\n<p><img src=\"http://source.mycookies.cn/201911210054_266.png?ERROR\" alt=\"\" /></p>\n<p>从案例中我们可能看出在单线程环境场景下cas的性能要高于锁相关的操作。当然，在竞争比较激烈的情况下性能可能会有所下降，因为<strong>要不断的重试和回退或者放弃操作</strong>，这也是CAS的一个缺点所在，因为这些重试，回退等操作通常用开发者来实现。</p>\n<p>CAS的实现并非是简单的代码层面控制的，而是需要硬件的支持，因此在不同的体系架构之间执行的性能差异很大。但是一个很管用的经验法则是：在大多数处理器上，在无竞争的锁获取和释放的”快速代码路径“上的开销，大约是CAS开销的两倍。</p>\n<h3><a id=\"CAS_104\"></a>为何CAS如此优秀</h3>\n<p><strong>硬件加持</strong>，现代大多数处理器都从硬件层面通过一些列指令实现CompareAndSwap(比较并交换)同步原语，进而使操作系统和JVM可以直接使用这些指令实现锁和并发的数据结构。我们可以简单认为，<strong>CAS是将比较和交换合成是一个原子操作</strong>。</p>\n<p><strong>JVM对CAS的支持</strong>， 由于Java程序运行在JVM上，所以应对不同的硬件体系架构的处理则需要JVM来实现。在不支持CAS操作的硬件上，jvm将使用自旋锁来实现。</p>\n<h3><a id=\"CASABA_110\"></a>CAS的ABA问题</h3>\n<p>cas操作让我们减少了锁带来的性能损耗，同时也给我们带来了新的麻烦-ABA问题。</p>\n<p><img src=\"http://source.mycookies.cn/201911210000_918.png?ERROR\" alt=\"\" /></p>\n<p>在线程A读取到x的值与执行CAS操作期间，线程B对x执行了两次修改，x的值从100变成200，然后再从200变回100；而后在线程A执行CAS操作过程中并未发现x发生过变化，成功修改了x的值。由于x的值100 -&gt;200-&gt;100,所以称之为ABA的原因。</p>\n<p>魔高一尺道高一丈，解决ABA的问题目前最常用的办法就是给数据加上“版本号”，每次修改数据时同时改变版本号即可。</p>\n<p><img src=\"http://source.mycookies.cn/201911210009_137.png?ERROR\" alt=\"\" /></p>\n<h3><a id=\"QA_122\"></a>Q&amp;A</h3>\n<p>在竞争比较激烈的情况下，CAS要进行回退，重试等操作才能得到正确的结果，那么CAS一定比加锁性能要高吗？</p>\n', '[{\"lev\":3,\"text\":\"Talk is cheap\",\"id\":\"Talk_is_cheap_0\"},{\"lev\":3,\"text\":\"Show you my code\",\"id\":\"Show_you_my_code_6\"},{\"lev\":3,\"text\":\"为何CAS如此优秀\",\"id\":\"CAS_104\"},{\"lev\":3,\"text\":\"CAS的ABA问题\",\"id\":\"CASABA_110\"},{\"lev\":3,\"text\":\"Q&A\",\"id\":\"QA_122\"}]', NULL, 0, 527, 17, NULL, 1574271014823, 1574271061625, 1);
INSERT INTO `blog` VALUES (58, 6, '关于代理模式相关的都在这了', '代理模式的坑今天终于填', '', '本文算是一篇关于面试填坑笔记，也是第一篇。在某次面试中被问到了“为什么jdk只能代理接口”的问题，当场暴毙~\n\n代理模式是一种理论上非常简单，但是各种地方的实现往往却非常复杂。本文将从代理模式的基本概念出发，探讨代理模式在java领域的应用与实现。读完本文你将get到以下几点：\n\n1. 为什么需要代理模式，它通常用来解决什么问题，以及代理模式的设计与实现思路\n2. Java领域中代理模式3种不同实现类型（静态代理，jdk动态代理，cglib）\n3. 代理模式的面试考点\n\n## 为什么要使用代理模式\n\n在生活中我们通常是去商场购买东西，而不是去工厂。最主要的原因可能有以下几种：\n\n1. 成本太高，去工厂路途遥远成本太高，并且可能从工厂进货要办理一些手续流程；\n2. 工厂不直接卖给你，毕竟可能设计到一些行业机密或者无良厂家有一些不想让你知道的东西；\n3. 商场能提供一些商品之外的服务，商场里有舒适的温度，整洁的洗手间，当然还有漂亮的小姐姐。\n\n在面向对象的系统中也有同样的问题**，有些对象由于某种原因，比如对象创建开销很大，或者某些操作需要安全控制等，直接访问会给使用者或者系统结构带来很多麻烦，这时我们就需要考虑使用代理模式**。\n\n在实践中我们可能会用代理模式解决以下问题：\n\n1. **权限控制与日志**， 在客户端请求接口时我们可能需要在调用之前对权限进行验证，或者通过记录接口调用前后时间，统计执行时长，又或者说我们需要记录用户的一些操作日志信息等，我们可以对原接口进行代理，然后根据需求在接口执行前后增加一些特定的操作。\n2. **重量级操作**， 比如创建开销大的对象， 可以先由代理对象扮演对象的替身，在需要的使用再创建对象，然后代理再将请求委托给真实的对象。\n\n## 什么是代理模式\n\n**代理模式：**为其他对象提供一种代理以控制（隔离，使用接口）对这个对象的访问。类图如下：\n\n ![](http://source.mycookies.cn/201911231959_447.png?ERROR)\n\n所谓控制，其实使用接口隔离其他对象与这个对象之间的交互；就是为client对象对RealSubject对象的访问一种隔离，本质上就是CLient→RealSuject的关系变成了Client→Subject,  Proxy→RealSubject。 需要注意的时，**代理类(Proxy)并不一定要求保持接口的完整的一致性（既也可以完全不需实现Subject接口），只要能够实现间接控制即可。**\n\n## 代理模式代码演进\n\n背景：假设已有一个订单系统，可以保存订单信息。\n\n需求：打印保存订单信息消耗时间。\n\n```java\n/**\n * 订单服务\n *\n * @author cruder\n * @date 2019-11-23 15:42\n **/\npublic class OrderService2 {\n    /**\n     * 保存订单接口\n     */\n    public void saveOrder(String orderInfo) throws InterruptedException {\n        // 随机休眠，模拟订单保存需要的时间\n        Thread.sleep(System.currentTimeMillis() & 100);\n        System.out.println(\"订单：\" + orderInfo + \"  保存成功\");\n    }\n}\n```\n\n### 普通方式实现\n\n 直接修改源代码，这通常也是最简单和最容易想到的实现。\n\n```java\n /**\n  * 保存订单接口, 直接修改代码\n  */\n public void saveOrder(String orderInfo) throws InterruptedException {\n \n     long start = System.currentTimeMillis();\n \n     // 随机休眠，模拟订单保存需要的时间\n     Thread.sleep(System.currentTimeMillis() & 100);\n     System.out.println(\"订单：\" + orderInfo + \"  保存成功\");\n \n     System.out.println(\"保存订单用时: \" + (System.currentTimeMillis() - start) + \"ms\");\n }\n```\n\n面向对象设计原则中的“开闭原则”告诉我们，开闭原则规定“软件中的对象（类，模块，函数等等）应该对于扩展是开放的，但是对于修改是封闭的”，这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。\n\n### 代理模式实现\n\n```java\n/**\n * 1. 定义接口，为了使代理被代理对象看起来一样。当然这一步完全可以省略\n *\n * @author cruder\n * @date 2019-11-23 15:58\n **/\npublic interface IOrderService {\n    /**\n     * 保存订单接口\n     * @param orderInfo 订单信息\n     */\n    void saveOrder(String orderInfo) throws InterruptedException;\n}\n/**\n * 2. 原有订单服务，也实现这个接口。注意 此步骤也完全可以省略。\n *\n * @author cruder\n * @date 2019-11-23 15:42\n **/\npublic class OrderService implements IOrderService{\n    /**\n     * 保存订单接口\n     */\n    @Override\n    public void saveOrder(String orderInfo) throws InterruptedException {\n        // 随机休眠，模拟订单保存需要的时间\n        Thread.sleep(System.currentTimeMillis() & 100);\n        System.out.println(\"订单：\" + orderInfo + \"  保存成功\");\n    }\n}\n\n\n/**\n * 3. 创建代理类，实现订单服务接口【这才是代理模式的实现】\n * \n * @author cruder\n * @date 2019-11-23 16:01\n **/\npublic class OrderServiceProxy implements IOrderService{\n    /**\n     * 内部持有真实的订单服务对象，保存订单工作实际由它来完成\n     */\n    private IOrderService orderService;\n\n    @Override\n    public void saveOrder(String orderInfo) throws InterruptedException {\n        /**\n         * 延迟初始化，也可以创建代理对象时就创建，或者作为构造参数传进来\n         * 仅作为代码实例，不考虑线程安全问题\n         */\n        if (orderService == null) {\n            orderService = new OrderService();\n        }\n\n        long start = System.currentTimeMillis();\n        orderService.saveOrder(orderInfo);\n        System.out.println(\"保存订单用时: \" + (System.currentTimeMillis() - start) + \"ms\");\n    }\n}\n```\n\n执行程序\n\n ![](http://source.mycookies.cn/201911232000_428.png?ERROR)\n\n### 代理模式的优缺点\n\n**优点：** 1、职责清晰。 2、高扩展性。 3、智能化。\n\n**缺点：**1、由于在客户端和真实主题之间增加了代理对象，因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作，有些代理模式的实现非常复杂。\n\n## Java三种代理模式的实现\n\n在java中代理模式可以按照代理类的创建时机分两类，即静态代理和动态代理，而动态代理又可以分为jdk动态代理和cglib动态代理。每种实现方式都各有千秋，接下来笔者将会针对不同的实现方式进行演示和剖析。\n\n## 静态代理\n\n在上文代理模式代码演进中就使用了静态代理模式。所谓静态代理中的“静”字，无非就是代理类的创建时机不同罢了。静态代理需要为每个被代理的对象手动创建一个代理类；而动态代理则时在运行时通过某种机制来动态生成，不需要手动创建代理类。\n\n## 动态代理 - jdk\n\njdk动态代理模式是利用java中的**反射技术，在运行时动态创建代理类**。接下来我们仍借助上文中的订单服务的案例，使用jdk动态代理实现。\n\n基于动态jdk涉及到**两个核心的类Proxy类和一个 InvocationHandler接口。**\n\n```java\n/**\n * 基于JDK技术 动态代理类技术核心 Proxy类和一个 InvocationHandler 接口\n *\n * @author cruder\n * @date 2019-11-23 16:40\n **/\npublic class ProxyFactory implements InvocationHandler {\n\n    /**\n     * 委托对象，既被代理的对象\n     */\n    private Object target;\n\n    public ProxyFactory (Object target) {\n        this.target = target;\n    }\n\n    /**\n     * 生成代理对象\n     * 1. Classloader loader: 制定当前被代理对象使用的累加子啊其，获取加载器的方法固定\n     * 2. Class<?>[] interfaces: 委托类的接口类型，使用泛型方法确认类型\n     * 3. InvocationHandler handler： 事件处理，执行委托对象的方法时会触发事件处理器方法，\n     * 会把当前执行的委托对象方法作为参数传入\n     */\n    public Object getProxyInstance() {\n        Class clazz = target.getClass();\n\n        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);\n    }\n\n	@Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        long start = System.currentTimeMillis();\n        method.invoke(target, args);\n        System.out.println(\"保存订单用时: \" + (System.currentTimeMillis() - start) + \"ms\");\n        return null;\n    }\n}\n\n/**\n * 通过动态代理方式来保存订单\n *\n * @author cruder\n * @date 2019-11-23 15:49\n **/\npublic class Client {\n    public static void main(String[] args) throws InterruptedException {\n        ProxyFactory proxyFactory= new ProxyFactory (new OrderService());\n        IOrderService orderService = (IOrderService) proxyFactory.getProxyInstance();\n        orderService.saveOrder(\" cruder 新买的花裤衩 \");\n    }\n}\n```\n\n ![](http://source.mycookies.cn/201911232002_705.png?ERROR)\n\n以上便是jdk动态代理的全部实现，有种只可意会不可言传的感觉，笔者始终感觉这种实现看起来很别扭。不过也要强行总结以下，jdk实现动态代理可以分为以下几个步骤：\n\n1. 先检查委托类是否实现了相应接口，保证被访问方法在接口中也要有定义\n2. 创建一个实现InvocationHandler接口的类\n3. 在类中定义一个被代理对象的成员属性，为了扩展方便可以直接使用Object类，也可以根据需求定义相应的接口\n4. 在invoke方法中实现对委托对象的调用，根据需求对方法进行增强\n5. 使用Proxy.newProxyInstance(...)方法创建代理对象，并提供要给获取代理对象的方法\n\n**代理类源码阅读**\n\n上文中基于jdk动态代理的代码实现中对于可*的产品经理来说已经完全满足了需求，但是对于具有Geek精神的程序员来说这远远不够，对于这种不知其所以然的东西往往让人感到不安。接下来我们将通过自定义的一个小工具类将动态生成的代理类保存到本地来一看究竟。\n\n```java\n/**\n * 将生成的代理类保存为.class文件的工具类\n *\n * @author cruder\n * @date 2019-08-15 0:27\n */\npublic class ProxyUtils {\n    /**\n     * 将代理类保存到指定路径\n     *\n     * @param path           保存到的路径\n     * @param proxyClassName 代理类的Class名称\n     * @param interfaces     代理类接口\n     * @return\n     */\n    public static boolean saveProxyClass(String path, String proxyClassName, Class[] interfaces){\n        if (proxyClassName == null || path == null) {\n            return false;\n        }\n        // 获取文件字节码，然后输出到目标文件中\n        byte[] classFile = ProxyGenerator.generateProxyClass(proxyClassName, interfaces);\n        try (FileOutputStream out = new FileOutputStream(path)) {\n            out.write(classFile);\n            out.flush();\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n        return true;\n    }\n}\n```\n\n![](C:/Users/mayn/Desktop/Export-fee80dfc-cc3a-4278-8434-28c90a3b9806/Untitled-726fc58f-198a-4f8d-9ed9-22f7a8d0e4e1.png)\n\n```java \n// 此处是重点， 生成的代理类实现了IOrderService，并且继承了Proxy\npublic final class $Proxy0 extends Proxy implements IOrderService {\n    private static Method m1;\n    private static Method m3;\n    private static Method m2;\n    private static Method m0;\n\n    public $Proxy0(InvocationHandler var1) throws  {\n        super(var1);\n    }\n\n    public final boolean equals(Object var1) throws  {\n        try {\n            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});\n        } catch (RuntimeException | Error var3) {\n            throw var3;\n        } catch (Throwable var4) {\n            throw new UndeclaredThrowableException(var4);\n        }\n    }\n\n    public final void saveOrder(Order var1) throws  {\n        try {\n            super.h.invoke(this, m3, new Object[]{var1});\n        } catch (RuntimeException | Error var3) {\n            throw var3;\n        } catch (Throwable var4) {\n            throw new UndeclaredThrowableException(var4);\n        }\n    }\n\n    public final String toString() throws  {\n        try {\n            return (String)super.h.invoke(this, m2, (Object[])null);\n        } catch (RuntimeException | Error var2) {\n            throw var2;\n        } catch (Throwable var3) {\n            throw new UndeclaredThrowableException(var3);\n        }\n    }\n\n    public final int hashCode() throws  {\n        try {\n            return (Integer)super.h.invoke(this, m0, (Object[])null);\n        } catch (RuntimeException | Error var2) {\n            throw var2;\n        } catch (Throwable var3) {\n            throw new UndeclaredThrowableException(var3);\n        }\n    }\n	\n    static {\n        try {\n		   // 通过反射获取Method对象\n            m1 = Class.forName(\"java.lang.Object\").getMethod(\"equals\", Class.forName(\"java.lang.Object\"));\n            m3 = Class.forName(\"cn.mycookies.test08proxy.IOrderService\").getMethod(\"saveOrder\", Class.forName(\"cn.mycookies.test08proxy.Order\"));\n            m2 = Class.forName(\"java.lang.Object\").getMethod(\"toString\");\n            m0 = Class.forName(\"java.lang.Object\").getMethod(\"hashCode\");\n        } catch (NoSuchMethodException var2) {\n            throw new NoSuchMethodError(var2.getMessage());\n        } catch (ClassNotFoundException var3) {\n            throw new NoClassDefFoundError(var3.getMessage());\n        }\n    }\n}\n```\n\nps： 实习转正面试中被问到为什么jdk动态代理被代理的类为什么要实现接口？\n\n## cglib动态代理\n\n对于cglib我想大多数人应该都很陌生，或者是在学习Spring中AOP（面向切面编程）时听说了它使用jdk和cglib两种方式实现了动态代理。接下来笔者将针对cglib进行简要介绍。\n\ncglib动态代理和jdk动态代理类似，也是采用**操作字节码机制**，在运行时生成代理类。cglib 动态代理采取的是**创建目标类的子类的方式**，因为是子类化，我们可以达到近似使用被调用者本身的效果。\n\n字节码处理机制-指得是ASM来转换字节码并生成新的类\n\n注：spring中有完整的cglib相关的依赖，所以以下代码基于spring官方下载的demo中直接进行编写的\n\n```java \n/**\n * 1. 订单服务-委托类，不需要再实现接口\n *\n * @author cruder\n * @date 2019-11-23 15:42\n **/\npublic class OrderService {\n    /**\n     * 保存订单接口\n     */\n    public void saveOrder(String orderInfo) throws InterruptedException {\n        // 随机休眠，模拟订单保存需要的时间\n        Thread.sleep(System.currentTimeMillis() & 100);\n        System.out.println(\"订单：\" + orderInfo + \"  保存成功\");\n    }\n}\n\n/**\n * cglib动态代理工厂\n *\n * @author cruder\n * @date 2019-11-23 18:36\n **/\npublic class ProxyFactory implements MethodInterceptor {\n\n    /**\n     * 委托对象， 即被代理对象\n      */\n    private Object target;\n\n    public ProxyFactory(Object target) {\n        this.target = target;\n    }\n\n    /**\n     * 返回一个代理对象\n     * @return\n     */\n    public Object getProxyInstance(){\n        // 1. 创建一个工具类\n        Enhancer enhancer = new Enhancer();\n        // 2. 设置父类\n        enhancer.setSuperclass(target.getClass());\n        // 3. 设置回调函数\n        enhancer.setCallback(this);\n        // 4.创建子类对象，即代理对象\n        return enhancer.create();\n    }\n\n    @Override\n    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {\n        long start = System.currentTimeMillis();\n\n        Object result = method.invoke(target, args);\n\n        System.out.println(\"cglib代理：保存订单用时: \" + (System.currentTimeMillis() - start) + \"ms\");\n        return result;\n    }\n}\n```\n\n​    \n\n```java\n/**\n * 使用cglib代理类来保存订单\n *\n * @author cruder\n * @date 2019-11-23 15:49\n **/\npublic class Client {\n    public static void main(String[] args) throws InterruptedException {\n        // 1. 创建委托对象\n        OrderService orderService = new OrderService();\n        // 2. 获取代理对象\n        OrderService orderServiceProxy = (OrderService) new ProxyFactory(orderService).getProxyInstance();\n        String saveFileName = \"CglibOrderServiceDynamicProxy.class\";\n        ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), new Class[]{IOrderService.class});\n        orderServiceProxy.saveOrder(\" cruder 新买的花裤衩 \");\n    }\n}\n```\n\n![](http://source.mycookies.cn/201911232003_694.png?ERROR)\n\ncglib动态代理实现步骤和jdk及其相似，可以分为以下几个步骤：\n\n1. 创建一个实现MethodInterceptor接口的类\n2. 在类中定义一个被代理对象的成员属性，为了扩展方便可以直接使用Object类，也可以根据需求定义相应的接口\n3. 在invoke方法中实现对委托对象的调用，根据需求对方法进行增强\n4. 使用Enhancer创建生成代理对象，并提供要给获取代理对象的方法\n\ncglib动态代理生成的代理类和jdk动态代理代码格式上几乎没有什么区别，唯一的区别在于cglib生成的代理类继承了仅仅Proxy类，而jdk动态代理生成的代理类继承了Proxy类的同时也实现了一个接口。代码如下：\n\n```java\n// 生成一个Proxy的子类\npublic final class OrderService extends Proxy {\n    private static Method m1;\n    private static Method m2;\n    private static Method m0;\n\n    public OrderService(InvocationHandler var1) throws  {\n        super(var1);\n    }\n\n    public final boolean equals(Object var1) throws  {\n        try {\n            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});\n        } catch (RuntimeException | Error var3) {\n            throw var3;\n        } catch (Throwable var4) {\n            throw new UndeclaredThrowableException(var4);\n        }\n    }\n\n    public final String toString() throws  {\n        try {\n            return (String)super.h.invoke(this, m2, (Object[])null);\n        } catch (RuntimeException | Error var2) {\n            throw var2;\n        } catch (Throwable var3) {\n            throw new UndeclaredThrowableException(var3);\n        }\n    }\n\n    public final int hashCode() throws  {\n        try {\n            return (Integer)super.h.invoke(this, m0, (Object[])null);\n        } catch (RuntimeException | Error var2) {\n            throw var2;\n        } catch (Throwable var3) {\n            throw new UndeclaredThrowableException(var3);\n        }\n    }\n\n    static {\n        try {\n            m1 = Class.forName(\"java.lang.Object\").getMethod(\"equals\", Class.forName(\"java.lang.Object\"));\n            m2 = Class.forName(\"java.lang.Object\").getMethod(\"toString\");\n            m0 = Class.forName(\"java.lang.Object\").getMethod(\"hashCode\");\n        } catch (NoSuchMethodException var2) {\n            throw new NoSuchMethodError(var2.getMessage());\n        } catch (ClassNotFoundException var3) {\n            throw new NoClassDefFoundError(var3.getMessage());\n        }\n    }\n}\n```\n\n## jdk动态代理  VS  cglib\n\nJDK Proxy 的优势：\n\n- 最小化依赖关系，减少依赖意味着简化开发和维护，JDK 本身的支持，可能比 cglib 更加可靠。\n- 平滑进行 JDK 版本升级，而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。\n- 代码实现简单。\n\ncglib 优势：\n\n- 有的时候调用目标可能不便实现额外接口，从某种角度看，限定调用者实现接口是有些侵入性的实践，类似 cglib 动态代理就没有这种限制。\n- 只操作我们关心的类，而不必为其他相关类增加工作量。\n\n## 总结\n\n1. 代理模式: 为其他对象提供一种代理以控制（隔离，使用接口）对这个对象的访问。\n2. jdk动态代理生成的代理类继承了Proxy类并实现了被代理的接口；而cglib生成的代理类则仅继承了Proxy类。\n3. jdk动态代理最大缺点：只能代理接口，既委托类必须实现相应的接口\n4. cglib缺点：由于是通过“子类化”的方式， 所以不能代理final的委托类或者普通委托类的final修饰的方法。\n\n## Q&A\n\n1. 为什么jdk动态代理只能代理接口？\n2. Spring中AOP的实现采用那种代理方式？\n3. 都说jdk动态代理性能远比cglib要差，如果是，依据是什么？', '<p>本文算是一篇关于面试填坑笔记，也是第一篇。在某次面试中被问到了“为什么jdk只能代理接口”的问题，当场暴毙~</p>\n<p>代理模式是一种理论上非常简单，但是各种地方的实现往往却非常复杂。本文将从代理模式的基本概念出发，探讨代理模式在java领域的应用与实现。读完本文你将get到以下几点：</p>\n<ol>\n<li>为什么需要代理模式，它通常用来解决什么问题，以及代理模式的设计与实现思路</li>\n<li>Java领域中代理模式3种不同实现类型（静态代理，jdk动态代理，cglib）</li>\n<li>代理模式的面试考点</li>\n</ol>\n<h2><a id=\"_8\"></a>为什么要使用代理模式</h2>\n<p>在生活中我们通常是去商场购买东西，而不是去工厂。最主要的原因可能有以下几种：</p>\n<ol>\n<li>成本太高，去工厂路途遥远成本太高，并且可能从工厂进货要办理一些手续流程；</li>\n<li>工厂不直接卖给你，毕竟可能设计到一些行业机密或者无良厂家有一些不想让你知道的东西；</li>\n<li>商场能提供一些商品之外的服务，商场里有舒适的温度，整洁的洗手间，当然还有漂亮的小姐姐。</li>\n</ol>\n<p>在面向对象的系统中也有同样的问题**，有些对象由于某种原因，比如对象创建开销很大，或者某些操作需要安全控制等，直接访问会给使用者或者系统结构带来很多麻烦，这时我们就需要考虑使用代理模式**。</p>\n<p>在实践中我们可能会用代理模式解决以下问题：</p>\n<ol>\n<li><strong>权限控制与日志</strong>， 在客户端请求接口时我们可能需要在调用之前对权限进行验证，或者通过记录接口调用前后时间，统计执行时长，又或者说我们需要记录用户的一些操作日志信息等，我们可以对原接口进行代理，然后根据需求在接口执行前后增加一些特定的操作。</li>\n<li><strong>重量级操作</strong>， 比如创建开销大的对象， 可以先由代理对象扮演对象的替身，在需要的使用再创建对象，然后代理再将请求委托给真实的对象。</li>\n</ol>\n<h2><a id=\"_23\"></a>什么是代理模式</h2>\n<p>**代理模式：**为其他对象提供一种代理以控制（隔离，使用接口）对这个对象的访问。类图如下：</p>\n<p><img src=\"http://source.mycookies.cn/201911231959_447.png?ERROR\" alt=\"\" /></p>\n<p>所谓控制，其实使用接口隔离其他对象与这个对象之间的交互；就是为client对象对RealSubject对象的访问一种隔离，本质上就是CLient→RealSuject的关系变成了Client→Subject,  Proxy→RealSubject。 需要注意的时，<strong>代理类(Proxy)并不一定要求保持接口的完整的一致性（既也可以完全不需实现Subject接口），只要能够实现间接控制即可。</strong></p>\n<h2><a id=\"_31\"></a>代理模式代码演进</h2>\n<p>背景：假设已有一个订单系统，可以保存订单信息。</p>\n<p>需求：打印保存订单信息消耗时间。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 订单服务\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:42\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderService2</span> </span>{\n    <span class=\"hljs-comment\">/**\n     * 保存订单接口\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">// 随机休眠，模拟订单保存需要的时间</span>\n        Thread.sleep(System.currentTimeMillis() &amp; <span class=\"hljs-number\">100</span>);\n        System.out.println(<span class=\"hljs-string\">\"订单：\"</span> + orderInfo + <span class=\"hljs-string\">\"  保存成功\"</span>);\n    }\n}\n</code></div></pre>\n<h3><a id=\"_56\"></a>普通方式实现</h3>\n<p>直接修改源代码，这通常也是最简单和最容易想到的实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"> <span class=\"hljs-comment\">/**\n  * 保存订单接口, 直接修改代码\n  */</span>\n <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n \n     <span class=\"hljs-keyword\">long</span> start = System.currentTimeMillis();\n \n     <span class=\"hljs-comment\">// 随机休眠，模拟订单保存需要的时间</span>\n     Thread.sleep(System.currentTimeMillis() &amp; <span class=\"hljs-number\">100</span>);\n     System.out.println(<span class=\"hljs-string\">\"订单：\"</span> + orderInfo + <span class=\"hljs-string\">\"  保存成功\"</span>);\n \n     System.out.println(<span class=\"hljs-string\">\"保存订单用时: \"</span> + (System.currentTimeMillis() - start) + <span class=\"hljs-string\">\"ms\"</span>);\n }\n</code></div></pre>\n<p>面向对象设计原则中的“开闭原则”告诉我们，开闭原则规定“软件中的对象（类，模块，函数等等）应该对于扩展是开放的，但是对于修改是封闭的”，这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。</p>\n<h3><a id=\"_78\"></a>代理模式实现</h3>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1. 定义接口，为了使代理被代理对象看起来一样。当然这一步完全可以省略\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:58\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface</span> <span class=\"hljs-title\">IOrderService</span> </span>{\n    <span class=\"hljs-comment\">/**\n     * 保存订单接口\n     * <span class=\"hljs-doctag\">@param</span> orderInfo 订单信息\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException</span>;\n}\n<span class=\"hljs-comment\">/**\n * 2. 原有订单服务，也实现这个接口。注意 此步骤也完全可以省略。\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:42\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderService</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">IOrderService</span></span>{\n    <span class=\"hljs-comment\">/**\n     * 保存订单接口\n     */</span>\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">// 随机休眠，模拟订单保存需要的时间</span>\n        Thread.sleep(System.currentTimeMillis() &amp; <span class=\"hljs-number\">100</span>);\n        System.out.println(<span class=\"hljs-string\">\"订单：\"</span> + orderInfo + <span class=\"hljs-string\">\"  保存成功\"</span>);\n    }\n}\n\n\n<span class=\"hljs-comment\">/**\n * 3. 创建代理类，实现订单服务接口【这才是代理模式的实现】\n * \n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 16:01\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderServiceProxy</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">IOrderService</span></span>{\n    <span class=\"hljs-comment\">/**\n     * 内部持有真实的订单服务对象，保存订单工作实际由它来完成\n     */</span>\n    <span class=\"hljs-keyword\">private</span> IOrderService orderService;\n\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">/**\n         * 延迟初始化，也可以创建代理对象时就创建，或者作为构造参数传进来\n         * 仅作为代码实例，不考虑线程安全问题\n         */</span>\n        <span class=\"hljs-keyword\">if</span> (orderService == <span class=\"hljs-keyword\">null</span>) {\n            orderService = <span class=\"hljs-keyword\">new</span> OrderService();\n        }\n\n        <span class=\"hljs-keyword\">long</span> start = System.currentTimeMillis();\n        orderService.saveOrder(orderInfo);\n        System.out.println(<span class=\"hljs-string\">\"保存订单用时: \"</span> + (System.currentTimeMillis() - start) + <span class=\"hljs-string\">\"ms\"</span>);\n    }\n}\n</code></div></pre>\n<p>执行程序</p>\n<p><img src=\"http://source.mycookies.cn/201911232000_428.png?ERROR\" alt=\"\" /></p>\n<h3><a id=\"_146\"></a>代理模式的优缺点</h3>\n<p><strong>优点：</strong> 1、职责清晰。 2、高扩展性。 3、智能化。</p>\n<p>**缺点：**1、由于在客户端和真实主题之间增加了代理对象，因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作，有些代理模式的实现非常复杂。</p>\n<h2><a id=\"Java_152\"></a>Java三种代理模式的实现</h2>\n<p>在java中代理模式可以按照代理类的创建时机分两类，即静态代理和动态代理，而动态代理又可以分为jdk动态代理和cglib动态代理。每种实现方式都各有千秋，接下来笔者将会针对不同的实现方式进行演示和剖析。</p>\n<h2><a id=\"_156\"></a>静态代理</h2>\n<p>在上文代理模式代码演进中就使用了静态代理模式。所谓静态代理中的“静”字，无非就是代理类的创建时机不同罢了。静态代理需要为每个被代理的对象手动创建一个代理类；而动态代理则时在运行时通过某种机制来动态生成，不需要手动创建代理类。</p>\n<h2><a id=\"__jdk_160\"></a>动态代理 - jdk</h2>\n<p>jdk动态代理模式是利用java中的<strong>反射技术，在运行时动态创建代理类</strong>。接下来我们仍借助上文中的订单服务的案例，使用jdk动态代理实现。</p>\n<p>基于动态jdk涉及到<strong>两个核心的类Proxy类和一个 InvocationHandler接口。</strong></p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 基于JDK技术 动态代理类技术核心 Proxy类和一个 InvocationHandler 接口\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 16:40\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ProxyFactory</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">InvocationHandler</span> </span>{\n\n    <span class=\"hljs-comment\">/**\n     * 委托对象，既被代理的对象\n     */</span>\n    <span class=\"hljs-keyword\">private</span> Object target;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-title\">ProxyFactory</span> <span class=\"hljs-params\">(Object target)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.target = target;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 生成代理对象\n     * 1. Classloader loader: 制定当前被代理对象使用的累加子啊其，获取加载器的方法固定\n     * 2. Class&lt;?&gt;[] interfaces: 委托类的接口类型，使用泛型方法确认类型\n     * 3. InvocationHandler handler： 事件处理，执行委托对象的方法时会触发事件处理器方法，\n     * 会把当前执行的委托对象方法作为参数传入\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> Object <span class=\"hljs-title\">getProxyInstance</span><span class=\"hljs-params\">()</span> </span>{\n        Class clazz = target.getClass();\n\n        <span class=\"hljs-keyword\">return</span> Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), <span class=\"hljs-keyword\">this</span>);\n    }\n\n	<span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> Object <span class=\"hljs-title\">invoke</span><span class=\"hljs-params\">(Object proxy, Method method, Object[] args)</span> <span class=\"hljs-keyword\">throws</span> Throwable </span>{\n        <span class=\"hljs-keyword\">long</span> start = System.currentTimeMillis();\n        method.invoke(target, args);\n        System.out.println(<span class=\"hljs-string\">\"保存订单用时: \"</span> + (System.currentTimeMillis() - start) + <span class=\"hljs-string\">\"ms\"</span>);\n        <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">null</span>;\n    }\n}\n\n<span class=\"hljs-comment\">/**\n * 通过动态代理方式来保存订单\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:49\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Client</span> </span>{\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">main</span><span class=\"hljs-params\">(String[] args)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        ProxyFactory proxyFactory= <span class=\"hljs-keyword\">new</span> ProxyFactory (<span class=\"hljs-keyword\">new</span> OrderService());\n        IOrderService orderService = (IOrderService) proxyFactory.getProxyInstance();\n        orderService.saveOrder(<span class=\"hljs-string\">\" cruder 新买的花裤衩 \"</span>);\n    }\n}\n</code></div></pre>\n<p><img src=\"http://source.mycookies.cn/201911232002_705.png?ERROR\" alt=\"\" /></p>\n<p>以上便是jdk动态代理的全部实现，有种只可意会不可言传的感觉，笔者始终感觉这种实现看起来很别扭。不过也要强行总结以下，jdk实现动态代理可以分为以下几个步骤：</p>\n<ol>\n<li>先检查委托类是否实现了相应接口，保证被访问方法在接口中也要有定义</li>\n<li>创建一个实现InvocationHandler接口的类</li>\n<li>在类中定义一个被代理对象的成员属性，为了扩展方便可以直接使用Object类，也可以根据需求定义相应的接口</li>\n<li>在invoke方法中实现对委托对象的调用，根据需求对方法进行增强</li>\n<li>使用Proxy.newProxyInstance(…)方法创建代理对象，并提供要给获取代理对象的方法</li>\n</ol>\n<p><strong>代理类源码阅读</strong></p>\n<p>上文中基于jdk动态代理的代码实现中对于可*的产品经理来说已经完全满足了需求，但是对于具有Geek精神的程序员来说这远远不够，对于这种不知其所以然的东西往往让人感到不安。接下来我们将通过自定义的一个小工具类将动态生成的代理类保存到本地来一看究竟。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 将生成的代理类保存为.class文件的工具类\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-08-15 0:27\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ProxyUtils</span> </span>{\n    <span class=\"hljs-comment\">/**\n     * 将代理类保存到指定路径\n     *\n     * <span class=\"hljs-doctag\">@param</span> path           保存到的路径\n     * <span class=\"hljs-doctag\">@param</span> proxyClassName 代理类的Class名称\n     * <span class=\"hljs-doctag\">@param</span> interfaces     代理类接口\n     * <span class=\"hljs-doctag\">@return</span>\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">saveProxyClass</span><span class=\"hljs-params\">(String path, String proxyClassName, Class[] interfaces)</span></span>{\n        <span class=\"hljs-keyword\">if</span> (proxyClassName == <span class=\"hljs-keyword\">null</span> || path == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">false</span>;\n        }\n        <span class=\"hljs-comment\">// 获取文件字节码，然后输出到目标文件中</span>\n        <span class=\"hljs-keyword\">byte</span>[] classFile = ProxyGenerator.generateProxyClass(proxyClassName, interfaces);\n        <span class=\"hljs-keyword\">try</span> (FileOutputStream out = <span class=\"hljs-keyword\">new</span> FileOutputStream(path)) {\n            out.write(classFile);\n            out.flush();\n        } <span class=\"hljs-keyword\">catch</span> (IOException e) {\n            e.printStackTrace();\n            <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">false</span>;\n        }\n        <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">true</span>;\n    }\n}\n</code></div></pre>\n<p><img src=\"C:/Users/mayn/Desktop/Export-fee80dfc-cc3a-4278-8434-28c90a3b9806/Untitled-726fc58f-198a-4f8d-9ed9-22f7a8d0e4e1.png\" alt=\"\" /></p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">// 此处是重点， 生成的代理类实现了IOrderService，并且继承了Proxy</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> $<span class=\"hljs-title\">Proxy0</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">Proxy</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">IOrderService</span> </span>{\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m1;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m3;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m2;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m0;\n\n    <span class=\"hljs-keyword\">public</span> $Proxy0(InvocationHandler var1) <span class=\"hljs-keyword\">throws</span>  {\n        <span class=\"hljs-keyword\">super</span>(var1);\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">equals</span><span class=\"hljs-params\">(Object var1)</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (Boolean)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m1, <span class=\"hljs-keyword\">new</span> Object[]{var1});\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var3) {\n            <span class=\"hljs-keyword\">throw</span> var3;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var4) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var4);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(Order var1)</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m3, <span class=\"hljs-keyword\">new</span> Object[]{var1});\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var3) {\n            <span class=\"hljs-keyword\">throw</span> var3;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var4) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var4);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> String <span class=\"hljs-title\">toString</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (String)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m2, (Object[])<span class=\"hljs-keyword\">null</span>);\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var2) {\n            <span class=\"hljs-keyword\">throw</span> var2;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var3);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">hashCode</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (Integer)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m0, (Object[])<span class=\"hljs-keyword\">null</span>);\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var2) {\n            <span class=\"hljs-keyword\">throw</span> var2;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var3);\n        }\n    }\n	\n    <span class=\"hljs-keyword\">static</span> {\n        <span class=\"hljs-keyword\">try</span> {\n		   <span class=\"hljs-comment\">// 通过反射获取Method对象</span>\n            m1 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"equals\"</span>, Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>));\n            m3 = Class.forName(<span class=\"hljs-string\">\"cn.mycookies.test08proxy.IOrderService\"</span>).getMethod(<span class=\"hljs-string\">\"saveOrder\"</span>, Class.forName(<span class=\"hljs-string\">\"cn.mycookies.test08proxy.Order\"</span>));\n            m2 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"toString\"</span>);\n            m0 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"hashCode\"</span>);\n        } <span class=\"hljs-keyword\">catch</span> (NoSuchMethodException var2) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> NoSuchMethodError(var2.getMessage());\n        } <span class=\"hljs-keyword\">catch</span> (ClassNotFoundException var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> NoClassDefFoundError(var3.getMessage());\n        }\n    }\n}\n</code></div></pre>\n<p>ps： 实习转正面试中被问到为什么jdk动态代理被代理的类为什么要实现接口？</p>\n<h2><a id=\"cglib_341\"></a>cglib动态代理</h2>\n<p>对于cglib我想大多数人应该都很陌生，或者是在学习Spring中AOP（面向切面编程）时听说了它使用jdk和cglib两种方式实现了动态代理。接下来笔者将针对cglib进行简要介绍。</p>\n<p>cglib动态代理和jdk动态代理类似，也是采用<strong>操作字节码机制</strong>，在运行时生成代理类。cglib 动态代理采取的是<strong>创建目标类的子类的方式</strong>，因为是子类化，我们可以达到近似使用被调用者本身的效果。</p>\n<p>字节码处理机制-指得是ASM来转换字节码并生成新的类</p>\n<p>注：spring中有完整的cglib相关的依赖，所以以下代码基于spring官方下载的demo中直接进行编写的</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1. 订单服务-委托类，不需要再实现接口\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:42\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderService</span> </span>{\n    <span class=\"hljs-comment\">/**\n     * 保存订单接口\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">// 随机休眠，模拟订单保存需要的时间</span>\n        Thread.sleep(System.currentTimeMillis() &amp; <span class=\"hljs-number\">100</span>);\n        System.out.println(<span class=\"hljs-string\">\"订单：\"</span> + orderInfo + <span class=\"hljs-string\">\"  保存成功\"</span>);\n    }\n}\n\n<span class=\"hljs-comment\">/**\n * cglib动态代理工厂\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 18:36\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ProxyFactory</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">MethodInterceptor</span> </span>{\n\n    <span class=\"hljs-comment\">/**\n     * 委托对象， 即被代理对象\n      */</span>\n    <span class=\"hljs-keyword\">private</span> Object target;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-title\">ProxyFactory</span><span class=\"hljs-params\">(Object target)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.target = target;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 返回一个代理对象\n     * <span class=\"hljs-doctag\">@return</span>\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> Object <span class=\"hljs-title\">getProxyInstance</span><span class=\"hljs-params\">()</span></span>{\n        <span class=\"hljs-comment\">// 1. 创建一个工具类</span>\n        Enhancer enhancer = <span class=\"hljs-keyword\">new</span> Enhancer();\n        <span class=\"hljs-comment\">// 2. 设置父类</span>\n        enhancer.setSuperclass(target.getClass());\n        <span class=\"hljs-comment\">// 3. 设置回调函数</span>\n        enhancer.setCallback(<span class=\"hljs-keyword\">this</span>);\n        <span class=\"hljs-comment\">// 4.创建子类对象，即代理对象</span>\n        <span class=\"hljs-keyword\">return</span> enhancer.create();\n    }\n\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> Object <span class=\"hljs-title\">intercept</span><span class=\"hljs-params\">(Object o, Method method, Object[] args, MethodProxy methodProxy)</span> <span class=\"hljs-keyword\">throws</span> Throwable </span>{\n        <span class=\"hljs-keyword\">long</span> start = System.currentTimeMillis();\n\n        Object result = method.invoke(target, args);\n\n        System.out.println(<span class=\"hljs-string\">\"cglib代理：保存订单用时: \"</span> + (System.currentTimeMillis() - start) + <span class=\"hljs-string\">\"ms\"</span>);\n        <span class=\"hljs-keyword\">return</span> result;\n    }\n}\n</code></div></pre>\n<p>​</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用cglib代理类来保存订单\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:49\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Client</span> </span>{\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">main</span><span class=\"hljs-params\">(String[] args)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">// 1. 创建委托对象</span>\n        OrderService orderService = <span class=\"hljs-keyword\">new</span> OrderService();\n        <span class=\"hljs-comment\">// 2. 获取代理对象</span>\n        OrderService orderServiceProxy = (OrderService) <span class=\"hljs-keyword\">new</span> ProxyFactory(orderService).getProxyInstance();\n        String saveFileName = <span class=\"hljs-string\">\"CglibOrderServiceDynamicProxy.class\"</span>;\n        ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), <span class=\"hljs-keyword\">new</span> Class[]{IOrderService.class});\n        orderServiceProxy.saveOrder(<span class=\"hljs-string\">\" cruder 新买的花裤衩 \"</span>);\n    }\n}\n</code></div></pre>\n<p><img src=\"http://source.mycookies.cn/201911232003_694.png?ERROR\" alt=\"\" /></p>\n<p>cglib动态代理实现步骤和jdk及其相似，可以分为以下几个步骤：</p>\n<ol>\n<li>创建一个实现MethodInterceptor接口的类</li>\n<li>在类中定义一个被代理对象的成员属性，为了扩展方便可以直接使用Object类，也可以根据需求定义相应的接口</li>\n<li>在invoke方法中实现对委托对象的调用，根据需求对方法进行增强</li>\n<li>使用Enhancer创建生成代理对象，并提供要给获取代理对象的方法</li>\n</ol>\n<p>cglib动态代理生成的代理类和jdk动态代理代码格式上几乎没有什么区别，唯一的区别在于cglib生成的代理类继承了仅仅Proxy类，而jdk动态代理生成的代理类继承了Proxy类的同时也实现了一个接口。代码如下：</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">// 生成一个Proxy的子类</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderService</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">Proxy</span> </span>{\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m1;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m2;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m0;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-title\">OrderService</span><span class=\"hljs-params\">(InvocationHandler var1)</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">super</span>(var1);\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">equals</span><span class=\"hljs-params\">(Object var1)</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (Boolean)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m1, <span class=\"hljs-keyword\">new</span> Object[]{var1});\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var3) {\n            <span class=\"hljs-keyword\">throw</span> var3;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var4) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var4);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> String <span class=\"hljs-title\">toString</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (String)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m2, (Object[])<span class=\"hljs-keyword\">null</span>);\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var2) {\n            <span class=\"hljs-keyword\">throw</span> var2;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var3);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">hashCode</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (Integer)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m0, (Object[])<span class=\"hljs-keyword\">null</span>);\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var2) {\n            <span class=\"hljs-keyword\">throw</span> var2;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var3);\n        }\n    }\n\n    <span class=\"hljs-keyword\">static</span> {\n        <span class=\"hljs-keyword\">try</span> {\n            m1 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"equals\"</span>, Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>));\n            m2 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"toString\"</span>);\n            m0 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"hashCode\"</span>);\n        } <span class=\"hljs-keyword\">catch</span> (NoSuchMethodException var2) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> NoSuchMethodError(var2.getMessage());\n        } <span class=\"hljs-keyword\">catch</span> (ClassNotFoundException var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> NoClassDefFoundError(var3.getMessage());\n        }\n    }\n}\n</code></div></pre>\n<h2><a id=\"jdk__VS__cglib_501\"></a>jdk动态代理  VS  cglib</h2>\n<p>JDK Proxy 的优势：</p>\n<ul>\n<li>最小化依赖关系，减少依赖意味着简化开发和维护，JDK 本身的支持，可能比 cglib 更加可靠。</li>\n<li>平滑进行 JDK 版本升级，而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。</li>\n<li>代码实现简单。</li>\n</ul>\n<p>cglib 优势：</p>\n<ul>\n<li>有的时候调用目标可能不便实现额外接口，从某种角度看，限定调用者实现接口是有些侵入性的实践，类似 cglib 动态代理就没有这种限制。</li>\n<li>只操作我们关心的类，而不必为其他相关类增加工作量。</li>\n</ul>\n<h2><a id=\"_514\"></a>总结</h2>\n<ol>\n<li>代理模式: 为其他对象提供一种代理以控制（隔离，使用接口）对这个对象的访问。</li>\n<li>jdk动态代理生成的代理类继承了Proxy类并实现了被代理的接口；而cglib生成的代理类则仅继承了Proxy类。</li>\n<li>jdk动态代理最大缺点：只能代理接口，既委托类必须实现相应的接口</li>\n<li>cglib缺点：由于是通过“子类化”的方式， 所以不能代理final的委托类或者普通委托类的final修饰的方法。</li>\n</ol>\n<h2><a id=\"QA_521\"></a>Q&amp;A</h2>\n<ol>\n<li>为什么jdk动态代理只能代理接口？</li>\n<li>Spring中AOP的实现采用那种代理方式？</li>\n<li>都说jdk动态代理性能远比cglib要差，如果是，依据是什么？</li>\n</ol>\n', '[{\"lev\":2,\"text\":\"为什么要使用代理模式\",\"id\":\"_8\"},{\"lev\":2,\"text\":\"什么是代理模式\",\"id\":\"_23\"},{\"lev\":2,\"text\":\"代理模式代码演进\",\"id\":\"_31\"},{\"lev\":3,\"text\":\"普通方式实现\",\"id\":\"_56\"},{\"lev\":3,\"text\":\"代理模式实现\",\"id\":\"_78\"},{\"lev\":3,\"text\":\"代理模式的优缺点\",\"id\":\"_146\"},{\"lev\":2,\"text\":\"Java三种代理模式的实现\",\"id\":\"Java_152\"},{\"lev\":2,\"text\":\"静态代理\",\"id\":\"_156\"},{\"lev\":2,\"text\":\"动态代理 - jdk\",\"id\":\"__jdk_160\"},{\"lev\":2,\"text\":\"cglib动态代理\",\"id\":\"cglib_341\"},{\"lev\":2,\"text\":\"jdk动态代理  VS  cglib\",\"id\":\"jdk__VS__cglib_501\"},{\"lev\":2,\"text\":\"总结\",\"id\":\"_514\"},{\"lev\":2,\"text\":\"Q&A\",\"id\":\"QA_521\"}]', NULL, 0, 1, 1, NULL, 1574510732044, 1574510745324, 0);
INSERT INTO `blog` VALUES (59, 6, '关于代理模式相关的都在这了', '代理模式的坑今天终于填上了', '', '本文算是一篇关于面试填坑笔记，也是第一篇。在某次面试中被问到了“为什么jdk只能代理接口”的问题，当场暴毙~\n\n代理模式是一种理论上非常简单，但是各种地方的实现往往却非常复杂。本文将从代理模式的基本概念出发，探讨代理模式在java领域的应用与实现。读完本文你将get到以下几点：\n\n1. 为什么需要代理模式，它通常用来解决什么问题，以及代理模式的设计与实现思路\n2. Java领域中代理模式3种不同实现类型（静态代理，jdk动态代理，cglib）\n3. 代理模式的面试考点\n\n## 为什么要使用代理模式\n\n在生活中我们通常是去商场购买东西，而不是去工厂。最主要的原因可能有以下几种：\n\n1. 成本太高，去工厂路途遥远成本太高，并且可能从工厂进货要办理一些手续流程；\n2. 工厂不直接卖给你，毕竟可能设计到一些行业机密或者无良厂家有一些不想让你知道的东西；\n3. 商场能提供一些商品之外的服务，商场里有舒适的温度，整洁的洗手间，当然还有漂亮的小姐姐。\n\n在面向对象的系统中也有同样的问题**，有些对象由于某种原因，比如对象创建开销很大，或者某些操作需要安全控制等，直接访问会给使用者或者系统结构带来很多麻烦，这时我们就需要考虑使用代理模式**。\n\n在实践中我们可能会用代理模式解决以下问题：\n\n1. **权限控制与日志**， 在客户端请求接口时我们可能需要在调用之前对权限进行验证，或者通过记录接口调用前后时间，统计执行时长，又或者说我们需要记录用户的一些操作日志信息等，我们可以对原接口进行代理，然后根据需求在接口执行前后增加一些特定的操作。\n2. **重量级操作**， 比如创建开销大的对象， 可以先由代理对象扮演对象的替身，在需要的使用再创建对象，然后代理再将请求委托给真实的对象。\n\n## 什么是代理模式\n\n**代理模式：**为其他对象提供一种代理以控制（隔离，使用接口）对这个对象的访问。类图如下：\n\n ![](http://source.mycookies.cn/201911231959_447.png?ERROR)\n\n所谓控制，其实使用接口隔离其他对象与这个对象之间的交互；就是为client对象对RealSubject对象的访问一种隔离，本质上就是CLient→RealSuject的关系变成了Client→Subject,  Proxy→RealSubject。 需要注意的时，**代理类(Proxy)并不一定要求保持接口的完整的一致性（既也可以完全不需实现Subject接口），只要能够实现间接控制即可。**\n\n## 代理模式代码演进\n\n背景：假设已有一个订单系统，可以保存订单信息。\n\n需求：打印保存订单信息消耗时间。\n\n```java\n/**\n * 订单服务\n *\n * @author cruder\n * @date 2019-11-23 15:42\n **/\npublic class OrderService2 {\n    /**\n     * 保存订单接口\n     */\n    public void saveOrder(String orderInfo) throws InterruptedException {\n        // 随机休眠，模拟订单保存需要的时间\n        Thread.sleep(System.currentTimeMillis() & 100);\n        System.out.println(\"订单：\" + orderInfo + \"  保存成功\");\n    }\n}\n```\n\n### 普通方式实现\n\n 直接修改源代码，这通常也是最简单和最容易想到的实现。\n\n```java\n /**\n  * 保存订单接口, 直接修改代码\n  */\n public void saveOrder(String orderInfo) throws InterruptedException {\n \n     long start = System.currentTimeMillis();\n \n     // 随机休眠，模拟订单保存需要的时间\n     Thread.sleep(System.currentTimeMillis() & 100);\n     System.out.println(\"订单：\" + orderInfo + \"  保存成功\");\n \n     System.out.println(\"保存订单用时: \" + (System.currentTimeMillis() - start) + \"ms\");\n }\n```\n\n面向对象设计原则中的“开闭原则”告诉我们，开闭原则规定“软件中的对象（类，模块，函数等等）应该对于扩展是开放的，但是对于修改是封闭的”，这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。\n\n### 代理模式实现\n\n```java\n/**\n * 1. 定义接口，为了使代理被代理对象看起来一样。当然这一步完全可以省略\n *\n * @author cruder\n * @date 2019-11-23 15:58\n **/\npublic interface IOrderService {\n    /**\n     * 保存订单接口\n     * @param orderInfo 订单信息\n     */\n    void saveOrder(String orderInfo) throws InterruptedException;\n}\n/**\n * 2. 原有订单服务，也实现这个接口。注意 此步骤也完全可以省略。\n *\n * @author cruder\n * @date 2019-11-23 15:42\n **/\npublic class OrderService implements IOrderService{\n    /**\n     * 保存订单接口\n     */\n    @Override\n    public void saveOrder(String orderInfo) throws InterruptedException {\n        // 随机休眠，模拟订单保存需要的时间\n        Thread.sleep(System.currentTimeMillis() & 100);\n        System.out.println(\"订单：\" + orderInfo + \"  保存成功\");\n    }\n}\n\n\n/**\n * 3. 创建代理类，实现订单服务接口【这才是代理模式的实现】\n * \n * @author cruder\n * @date 2019-11-23 16:01\n **/\npublic class OrderServiceProxy implements IOrderService{\n    /**\n     * 内部持有真实的订单服务对象，保存订单工作实际由它来完成\n     */\n    private IOrderService orderService;\n\n    @Override\n    public void saveOrder(String orderInfo) throws InterruptedException {\n        /**\n         * 延迟初始化，也可以创建代理对象时就创建，或者作为构造参数传进来\n         * 仅作为代码实例，不考虑线程安全问题\n         */\n        if (orderService == null) {\n            orderService = new OrderService();\n        }\n\n        long start = System.currentTimeMillis();\n        orderService.saveOrder(orderInfo);\n        System.out.println(\"保存订单用时: \" + (System.currentTimeMillis() - start) + \"ms\");\n    }\n}\n```\n\n执行程序\n\n ![](http://source.mycookies.cn/201911232000_428.png?ERROR)\n\n### 代理模式的优缺点\n\n**优点：** 1、职责清晰。 2、高扩展性。 3、智能化。\n\n**缺点：**1、由于在客户端和真实主题之间增加了代理对象，因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作，有些代理模式的实现非常复杂。\n\n## Java三种代理模式的实现\n\n在java中代理模式可以按照代理类的创建时机分两类，即静态代理和动态代理，而动态代理又可以分为jdk动态代理和cglib动态代理。每种实现方式都各有千秋，接下来笔者将会针对不同的实现方式进行演示和剖析。\n\n## 静态代理\n\n在上文代理模式代码演进中就使用了静态代理模式。所谓静态代理中的“静”字，无非就是代理类的创建时机不同罢了。静态代理需要为每个被代理的对象手动创建一个代理类；而动态代理则时在运行时通过某种机制来动态生成，不需要手动创建代理类。\n\n## 动态代理 - jdk\n\njdk动态代理模式是利用java中的**反射技术，在运行时动态创建代理类**。接下来我们仍借助上文中的订单服务的案例，使用jdk动态代理实现。\n\n基于动态jdk涉及到**两个核心的类Proxy类和一个 InvocationHandler接口。**\n\n```java\n/**\n * 基于JDK技术 动态代理类技术核心 Proxy类和一个 InvocationHandler 接口\n *\n * @author cruder\n * @date 2019-11-23 16:40\n **/\npublic class ProxyFactory implements InvocationHandler {\n\n    /**\n     * 委托对象，既被代理的对象\n     */\n    private Object target;\n\n    public ProxyFactory (Object target) {\n        this.target = target;\n    }\n\n    /**\n     * 生成代理对象\n     * 1. Classloader loader: 制定当前被代理对象使用的累加子啊其，获取加载器的方法固定\n     * 2. Class<?>[] interfaces: 委托类的接口类型，使用泛型方法确认类型\n     * 3. InvocationHandler handler： 事件处理，执行委托对象的方法时会触发事件处理器方法，\n     * 会把当前执行的委托对象方法作为参数传入\n     */\n    public Object getProxyInstance() {\n        Class clazz = target.getClass();\n\n        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);\n    }\n\n	@Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        long start = System.currentTimeMillis();\n        method.invoke(target, args);\n        System.out.println(\"保存订单用时: \" + (System.currentTimeMillis() - start) + \"ms\");\n        return null;\n    }\n}\n\n/**\n * 通过动态代理方式来保存订单\n *\n * @author cruder\n * @date 2019-11-23 15:49\n **/\npublic class Client {\n    public static void main(String[] args) throws InterruptedException {\n        ProxyFactory proxyFactory= new ProxyFactory (new OrderService());\n        IOrderService orderService = (IOrderService) proxyFactory.getProxyInstance();\n        orderService.saveOrder(\" cruder 新买的花裤衩 \");\n    }\n}\n```\n\n ![](http://source.mycookies.cn/201911232002_705.png?ERROR)\n\n以上便是jdk动态代理的全部实现，有种只可意会不可言传的感觉，笔者始终感觉这种实现看起来很别扭。不过也要强行总结以下，jdk实现动态代理可以分为以下几个步骤：\n\n1. 先检查委托类是否实现了相应接口，保证被访问方法在接口中也要有定义\n2. 创建一个实现InvocationHandler接口的类\n3. 在类中定义一个被代理对象的成员属性，为了扩展方便可以直接使用Object类，也可以根据需求定义相应的接口\n4. 在invoke方法中实现对委托对象的调用，根据需求对方法进行增强\n5. 使用Proxy.newProxyInstance(...)方法创建代理对象，并提供要给获取代理对象的方法\n\n**代理类源码阅读**\n\n上文中基于jdk动态代理的代码实现中对于可*的产品经理来说已经完全满足了需求，但是对于具有Geek精神的程序员来说这远远不够，对于这种不知其所以然的东西往往让人感到不安。接下来我们将通过自定义的一个小工具类将动态生成的代理类保存到本地来一看究竟。\n\n```java\n/**\n * 将生成的代理类保存为.class文件的工具类\n *\n * @author cruder\n * @date 2019-08-15 0:27\n */\npublic class ProxyUtils {\n    /**\n     * 将代理类保存到指定路径\n     *\n     * @param path           保存到的路径\n     * @param proxyClassName 代理类的Class名称\n     * @param interfaces     代理类接口\n     * @return\n     */\n    public static boolean saveProxyClass(String path, String proxyClassName, Class[] interfaces){\n        if (proxyClassName == null || path == null) {\n            return false;\n        }\n        // 获取文件字节码，然后输出到目标文件中\n        byte[] classFile = ProxyGenerator.generateProxyClass(proxyClassName, interfaces);\n        try (FileOutputStream out = new FileOutputStream(path)) {\n            out.write(classFile);\n            out.flush();\n        } catch (IOException e) {\n            e.printStackTrace();\n            return false;\n        }\n        return true;\n    }\n}\n```\n\n![](C:/Users/mayn/Desktop/Export-fee80dfc-cc3a-4278-8434-28c90a3b9806/Untitled-726fc58f-198a-4f8d-9ed9-22f7a8d0e4e1.png)\n\n```java \n// 此处是重点， 生成的代理类实现了IOrderService，并且继承了Proxy\npublic final class $Proxy0 extends Proxy implements IOrderService {\n    private static Method m1;\n    private static Method m3;\n    private static Method m2;\n    private static Method m0;\n\n    public $Proxy0(InvocationHandler var1) throws  {\n        super(var1);\n    }\n\n    public final boolean equals(Object var1) throws  {\n        try {\n            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});\n        } catch (RuntimeException | Error var3) {\n            throw var3;\n        } catch (Throwable var4) {\n            throw new UndeclaredThrowableException(var4);\n        }\n    }\n\n    public final void saveOrder(Order var1) throws  {\n        try {\n            super.h.invoke(this, m3, new Object[]{var1});\n        } catch (RuntimeException | Error var3) {\n            throw var3;\n        } catch (Throwable var4) {\n            throw new UndeclaredThrowableException(var4);\n        }\n    }\n\n    public final String toString() throws  {\n        try {\n            return (String)super.h.invoke(this, m2, (Object[])null);\n        } catch (RuntimeException | Error var2) {\n            throw var2;\n        } catch (Throwable var3) {\n            throw new UndeclaredThrowableException(var3);\n        }\n    }\n\n    public final int hashCode() throws  {\n        try {\n            return (Integer)super.h.invoke(this, m0, (Object[])null);\n        } catch (RuntimeException | Error var2) {\n            throw var2;\n        } catch (Throwable var3) {\n            throw new UndeclaredThrowableException(var3);\n        }\n    }\n	\n    static {\n        try {\n		   // 通过反射获取Method对象\n            m1 = Class.forName(\"java.lang.Object\").getMethod(\"equals\", Class.forName(\"java.lang.Object\"));\n            m3 = Class.forName(\"cn.mycookies.test08proxy.IOrderService\").getMethod(\"saveOrder\", Class.forName(\"cn.mycookies.test08proxy.Order\"));\n            m2 = Class.forName(\"java.lang.Object\").getMethod(\"toString\");\n            m0 = Class.forName(\"java.lang.Object\").getMethod(\"hashCode\");\n        } catch (NoSuchMethodException var2) {\n            throw new NoSuchMethodError(var2.getMessage());\n        } catch (ClassNotFoundException var3) {\n            throw new NoClassDefFoundError(var3.getMessage());\n        }\n    }\n}\n```\n\nps： 实习转正面试中被问到为什么jdk动态代理被代理的类为什么要实现接口？\n\n## cglib动态代理\n\n对于cglib我想大多数人应该都很陌生，或者是在学习Spring中AOP（面向切面编程）时听说了它使用jdk和cglib两种方式实现了动态代理。接下来笔者将针对cglib进行简要介绍。\n\ncglib动态代理和jdk动态代理类似，也是采用**操作字节码机制**，在运行时生成代理类。cglib 动态代理采取的是**创建目标类的子类的方式**，因为是子类化，我们可以达到近似使用被调用者本身的效果。\n\n字节码处理机制-指得是ASM来转换字节码并生成新的类\n\n注：spring中有完整的cglib相关的依赖，所以以下代码基于spring官方下载的demo中直接进行编写的\n\n```java \n/**\n * 1. 订单服务-委托类，不需要再实现接口\n *\n * @author cruder\n * @date 2019-11-23 15:42\n **/\npublic class OrderService {\n    /**\n     * 保存订单接口\n     */\n    public void saveOrder(String orderInfo) throws InterruptedException {\n        // 随机休眠，模拟订单保存需要的时间\n        Thread.sleep(System.currentTimeMillis() & 100);\n        System.out.println(\"订单：\" + orderInfo + \"  保存成功\");\n    }\n}\n\n/**\n * cglib动态代理工厂\n *\n * @author cruder\n * @date 2019-11-23 18:36\n **/\npublic class ProxyFactory implements MethodInterceptor {\n\n    /**\n     * 委托对象， 即被代理对象\n      */\n    private Object target;\n\n    public ProxyFactory(Object target) {\n        this.target = target;\n    }\n\n    /**\n     * 返回一个代理对象\n     * @return\n     */\n    public Object getProxyInstance(){\n        // 1. 创建一个工具类\n        Enhancer enhancer = new Enhancer();\n        // 2. 设置父类\n        enhancer.setSuperclass(target.getClass());\n        // 3. 设置回调函数\n        enhancer.setCallback(this);\n        // 4.创建子类对象，即代理对象\n        return enhancer.create();\n    }\n\n    @Override\n    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {\n        long start = System.currentTimeMillis();\n\n        Object result = method.invoke(target, args);\n\n        System.out.println(\"cglib代理：保存订单用时: \" + (System.currentTimeMillis() - start) + \"ms\");\n        return result;\n    }\n}\n```\n\n​    \n\n```java\n/**\n * 使用cglib代理类来保存订单\n *\n * @author cruder\n * @date 2019-11-23 15:49\n **/\npublic class Client {\n    public static void main(String[] args) throws InterruptedException {\n        // 1. 创建委托对象\n        OrderService orderService = new OrderService();\n        // 2. 获取代理对象\n        OrderService orderServiceProxy = (OrderService) new ProxyFactory(orderService).getProxyInstance();\n        String saveFileName = \"CglibOrderServiceDynamicProxy.class\";\n        ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), new Class[]{IOrderService.class});\n        orderServiceProxy.saveOrder(\" cruder 新买的花裤衩 \");\n    }\n}\n```\n\n![](http://source.mycookies.cn/201911232003_694.png?ERROR)\n\ncglib动态代理实现步骤和jdk及其相似，可以分为以下几个步骤：\n\n1. 创建一个实现MethodInterceptor接口的类\n2. 在类中定义一个被代理对象的成员属性，为了扩展方便可以直接使用Object类，也可以根据需求定义相应的接口\n3. 在invoke方法中实现对委托对象的调用，根据需求对方法进行增强\n4. 使用Enhancer创建生成代理对象，并提供要给获取代理对象的方法\n\ncglib动态代理生成的代理类和jdk动态代理代码格式上几乎没有什么区别，唯一的区别在于cglib生成的代理类继承了仅仅Proxy类，而jdk动态代理生成的代理类继承了Proxy类的同时也实现了一个接口。代码如下：\n\n```java\n// 生成一个Proxy的子类\npublic final class OrderService extends Proxy {\n    private static Method m1;\n    private static Method m2;\n    private static Method m0;\n\n    public OrderService(InvocationHandler var1) throws  {\n        super(var1);\n    }\n\n    public final boolean equals(Object var1) throws  {\n        try {\n            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});\n        } catch (RuntimeException | Error var3) {\n            throw var3;\n        } catch (Throwable var4) {\n            throw new UndeclaredThrowableException(var4);\n        }\n    }\n\n    public final String toString() throws  {\n        try {\n            return (String)super.h.invoke(this, m2, (Object[])null);\n        } catch (RuntimeException | Error var2) {\n            throw var2;\n        } catch (Throwable var3) {\n            throw new UndeclaredThrowableException(var3);\n        }\n    }\n\n    public final int hashCode() throws  {\n        try {\n            return (Integer)super.h.invoke(this, m0, (Object[])null);\n        } catch (RuntimeException | Error var2) {\n            throw var2;\n        } catch (Throwable var3) {\n            throw new UndeclaredThrowableException(var3);\n        }\n    }\n\n    static {\n        try {\n            m1 = Class.forName(\"java.lang.Object\").getMethod(\"equals\", Class.forName(\"java.lang.Object\"));\n            m2 = Class.forName(\"java.lang.Object\").getMethod(\"toString\");\n            m0 = Class.forName(\"java.lang.Object\").getMethod(\"hashCode\");\n        } catch (NoSuchMethodException var2) {\n            throw new NoSuchMethodError(var2.getMessage());\n        } catch (ClassNotFoundException var3) {\n            throw new NoClassDefFoundError(var3.getMessage());\n        }\n    }\n}\n```\n\n## jdk动态代理  VS  cglib\n\nJDK Proxy 的优势：\n\n- 最小化依赖关系，减少依赖意味着简化开发和维护，JDK 本身的支持，可能比 cglib 更加可靠。\n- 平滑进行 JDK 版本升级，而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。\n- 代码实现简单。\n\ncglib 优势：\n\n- 有的时候调用目标可能不便实现额外接口，从某种角度看，限定调用者实现接口是有些侵入性的实践，类似 cglib 动态代理就没有这种限制。\n- 只操作我们关心的类，而不必为其他相关类增加工作量。\n\n## 总结\n\n1. 代理模式: 为其他对象提供一种代理以控制（隔离，使用接口）对这个对象的访问。\n2. jdk动态代理生成的代理类继承了Proxy类并实现了被代理的接口；而cglib生成的代理类则仅继承了Proxy类。\n3. jdk动态代理最大缺点：只能代理接口，既委托类必须实现相应的接口\n4. cglib缺点：由于是通过“子类化”的方式， 所以不能代理final的委托类或者普通委托类的final修饰的方法。\n\n## Q&A\n\n1. 为什么jdk动态代理只能代理接口？\n2. Spring中AOP的实现采用那种代理方式？\n3. 都说jdk动态代理性能远比cglib要差，如果是，依据是什么？', '<p>本文算是一篇关于面试填坑笔记，也是第一篇。在某次面试中被问到了“为什么jdk只能代理接口”的问题，当场暴毙~</p>\n<p>代理模式是一种理论上非常简单，但是各种地方的实现往往却非常复杂。本文将从代理模式的基本概念出发，探讨代理模式在java领域的应用与实现。读完本文你将get到以下几点：</p>\n<ol>\n<li>为什么需要代理模式，它通常用来解决什么问题，以及代理模式的设计与实现思路</li>\n<li>Java领域中代理模式3种不同实现类型（静态代理，jdk动态代理，cglib）</li>\n<li>代理模式的面试考点</li>\n</ol>\n<h2><a id=\"_8\"></a>为什么要使用代理模式</h2>\n<p>在生活中我们通常是去商场购买东西，而不是去工厂。最主要的原因可能有以下几种：</p>\n<ol>\n<li>成本太高，去工厂路途遥远成本太高，并且可能从工厂进货要办理一些手续流程；</li>\n<li>工厂不直接卖给你，毕竟可能设计到一些行业机密或者无良厂家有一些不想让你知道的东西；</li>\n<li>商场能提供一些商品之外的服务，商场里有舒适的温度，整洁的洗手间，当然还有漂亮的小姐姐。</li>\n</ol>\n<p>在面向对象的系统中也有同样的问题**，有些对象由于某种原因，比如对象创建开销很大，或者某些操作需要安全控制等，直接访问会给使用者或者系统结构带来很多麻烦，这时我们就需要考虑使用代理模式**。</p>\n<p>在实践中我们可能会用代理模式解决以下问题：</p>\n<ol>\n<li><strong>权限控制与日志</strong>， 在客户端请求接口时我们可能需要在调用之前对权限进行验证，或者通过记录接口调用前后时间，统计执行时长，又或者说我们需要记录用户的一些操作日志信息等，我们可以对原接口进行代理，然后根据需求在接口执行前后增加一些特定的操作。</li>\n<li><strong>重量级操作</strong>， 比如创建开销大的对象， 可以先由代理对象扮演对象的替身，在需要的使用再创建对象，然后代理再将请求委托给真实的对象。</li>\n</ol>\n<h2><a id=\"_23\"></a>什么是代理模式</h2>\n<p>**代理模式：**为其他对象提供一种代理以控制（隔离，使用接口）对这个对象的访问。类图如下：</p>\n<p><img src=\"http://source.mycookies.cn/201911231959_447.png?ERROR\" alt=\"\" /></p>\n<p>所谓控制，其实使用接口隔离其他对象与这个对象之间的交互；就是为client对象对RealSubject对象的访问一种隔离，本质上就是CLient→RealSuject的关系变成了Client→Subject,  Proxy→RealSubject。 需要注意的时，<strong>代理类(Proxy)并不一定要求保持接口的完整的一致性（既也可以完全不需实现Subject接口），只要能够实现间接控制即可。</strong></p>\n<h2><a id=\"_31\"></a>代理模式代码演进</h2>\n<p>背景：假设已有一个订单系统，可以保存订单信息。</p>\n<p>需求：打印保存订单信息消耗时间。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 订单服务\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:42\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderService2</span> </span>{\n    <span class=\"hljs-comment\">/**\n     * 保存订单接口\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">// 随机休眠，模拟订单保存需要的时间</span>\n        Thread.sleep(System.currentTimeMillis() &amp; <span class=\"hljs-number\">100</span>);\n        System.out.println(<span class=\"hljs-string\">\"订单：\"</span> + orderInfo + <span class=\"hljs-string\">\"  保存成功\"</span>);\n    }\n}\n</code></div></pre>\n<h3><a id=\"_56\"></a>普通方式实现</h3>\n<p>直接修改源代码，这通常也是最简单和最容易想到的实现。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"> <span class=\"hljs-comment\">/**\n  * 保存订单接口, 直接修改代码\n  */</span>\n <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n \n     <span class=\"hljs-keyword\">long</span> start = System.currentTimeMillis();\n \n     <span class=\"hljs-comment\">// 随机休眠，模拟订单保存需要的时间</span>\n     Thread.sleep(System.currentTimeMillis() &amp; <span class=\"hljs-number\">100</span>);\n     System.out.println(<span class=\"hljs-string\">\"订单：\"</span> + orderInfo + <span class=\"hljs-string\">\"  保存成功\"</span>);\n \n     System.out.println(<span class=\"hljs-string\">\"保存订单用时: \"</span> + (System.currentTimeMillis() - start) + <span class=\"hljs-string\">\"ms\"</span>);\n }\n</code></div></pre>\n<p>面向对象设计原则中的“开闭原则”告诉我们，开闭原则规定“软件中的对象（类，模块，函数等等）应该对于扩展是开放的，但是对于修改是封闭的”，这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。</p>\n<h3><a id=\"_78\"></a>代理模式实现</h3>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1. 定义接口，为了使代理被代理对象看起来一样。当然这一步完全可以省略\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:58\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface</span> <span class=\"hljs-title\">IOrderService</span> </span>{\n    <span class=\"hljs-comment\">/**\n     * 保存订单接口\n     * <span class=\"hljs-doctag\">@param</span> orderInfo 订单信息\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException</span>;\n}\n<span class=\"hljs-comment\">/**\n * 2. 原有订单服务，也实现这个接口。注意 此步骤也完全可以省略。\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:42\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderService</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">IOrderService</span></span>{\n    <span class=\"hljs-comment\">/**\n     * 保存订单接口\n     */</span>\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">// 随机休眠，模拟订单保存需要的时间</span>\n        Thread.sleep(System.currentTimeMillis() &amp; <span class=\"hljs-number\">100</span>);\n        System.out.println(<span class=\"hljs-string\">\"订单：\"</span> + orderInfo + <span class=\"hljs-string\">\"  保存成功\"</span>);\n    }\n}\n\n\n<span class=\"hljs-comment\">/**\n * 3. 创建代理类，实现订单服务接口【这才是代理模式的实现】\n * \n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 16:01\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderServiceProxy</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">IOrderService</span></span>{\n    <span class=\"hljs-comment\">/**\n     * 内部持有真实的订单服务对象，保存订单工作实际由它来完成\n     */</span>\n    <span class=\"hljs-keyword\">private</span> IOrderService orderService;\n\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">/**\n         * 延迟初始化，也可以创建代理对象时就创建，或者作为构造参数传进来\n         * 仅作为代码实例，不考虑线程安全问题\n         */</span>\n        <span class=\"hljs-keyword\">if</span> (orderService == <span class=\"hljs-keyword\">null</span>) {\n            orderService = <span class=\"hljs-keyword\">new</span> OrderService();\n        }\n\n        <span class=\"hljs-keyword\">long</span> start = System.currentTimeMillis();\n        orderService.saveOrder(orderInfo);\n        System.out.println(<span class=\"hljs-string\">\"保存订单用时: \"</span> + (System.currentTimeMillis() - start) + <span class=\"hljs-string\">\"ms\"</span>);\n    }\n}\n</code></div></pre>\n<p>执行程序</p>\n<p><img src=\"http://source.mycookies.cn/201911232000_428.png?ERROR\" alt=\"\" /></p>\n<h3><a id=\"_146\"></a>代理模式的优缺点</h3>\n<p><strong>优点：</strong> 1、职责清晰。 2、高扩展性。 3、智能化。</p>\n<p>**缺点：**1、由于在客户端和真实主题之间增加了代理对象，因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作，有些代理模式的实现非常复杂。</p>\n<h2><a id=\"Java_152\"></a>Java三种代理模式的实现</h2>\n<p>在java中代理模式可以按照代理类的创建时机分两类，即静态代理和动态代理，而动态代理又可以分为jdk动态代理和cglib动态代理。每种实现方式都各有千秋，接下来笔者将会针对不同的实现方式进行演示和剖析。</p>\n<h2><a id=\"_156\"></a>静态代理</h2>\n<p>在上文代理模式代码演进中就使用了静态代理模式。所谓静态代理中的“静”字，无非就是代理类的创建时机不同罢了。静态代理需要为每个被代理的对象手动创建一个代理类；而动态代理则时在运行时通过某种机制来动态生成，不需要手动创建代理类。</p>\n<h2><a id=\"__jdk_160\"></a>动态代理 - jdk</h2>\n<p>jdk动态代理模式是利用java中的<strong>反射技术，在运行时动态创建代理类</strong>。接下来我们仍借助上文中的订单服务的案例，使用jdk动态代理实现。</p>\n<p>基于动态jdk涉及到<strong>两个核心的类Proxy类和一个 InvocationHandler接口。</strong></p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 基于JDK技术 动态代理类技术核心 Proxy类和一个 InvocationHandler 接口\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 16:40\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ProxyFactory</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">InvocationHandler</span> </span>{\n\n    <span class=\"hljs-comment\">/**\n     * 委托对象，既被代理的对象\n     */</span>\n    <span class=\"hljs-keyword\">private</span> Object target;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-title\">ProxyFactory</span> <span class=\"hljs-params\">(Object target)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.target = target;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 生成代理对象\n     * 1. Classloader loader: 制定当前被代理对象使用的累加子啊其，获取加载器的方法固定\n     * 2. Class&lt;?&gt;[] interfaces: 委托类的接口类型，使用泛型方法确认类型\n     * 3. InvocationHandler handler： 事件处理，执行委托对象的方法时会触发事件处理器方法，\n     * 会把当前执行的委托对象方法作为参数传入\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> Object <span class=\"hljs-title\">getProxyInstance</span><span class=\"hljs-params\">()</span> </span>{\n        Class clazz = target.getClass();\n\n        <span class=\"hljs-keyword\">return</span> Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), <span class=\"hljs-keyword\">this</span>);\n    }\n\n	<span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> Object <span class=\"hljs-title\">invoke</span><span class=\"hljs-params\">(Object proxy, Method method, Object[] args)</span> <span class=\"hljs-keyword\">throws</span> Throwable </span>{\n        <span class=\"hljs-keyword\">long</span> start = System.currentTimeMillis();\n        method.invoke(target, args);\n        System.out.println(<span class=\"hljs-string\">\"保存订单用时: \"</span> + (System.currentTimeMillis() - start) + <span class=\"hljs-string\">\"ms\"</span>);\n        <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">null</span>;\n    }\n}\n\n<span class=\"hljs-comment\">/**\n * 通过动态代理方式来保存订单\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:49\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Client</span> </span>{\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">main</span><span class=\"hljs-params\">(String[] args)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        ProxyFactory proxyFactory= <span class=\"hljs-keyword\">new</span> ProxyFactory (<span class=\"hljs-keyword\">new</span> OrderService());\n        IOrderService orderService = (IOrderService) proxyFactory.getProxyInstance();\n        orderService.saveOrder(<span class=\"hljs-string\">\" cruder 新买的花裤衩 \"</span>);\n    }\n}\n</code></div></pre>\n<p><img src=\"http://source.mycookies.cn/201911232002_705.png?ERROR\" alt=\"\" /></p>\n<p>以上便是jdk动态代理的全部实现，有种只可意会不可言传的感觉，笔者始终感觉这种实现看起来很别扭。不过也要强行总结以下，jdk实现动态代理可以分为以下几个步骤：</p>\n<ol>\n<li>先检查委托类是否实现了相应接口，保证被访问方法在接口中也要有定义</li>\n<li>创建一个实现InvocationHandler接口的类</li>\n<li>在类中定义一个被代理对象的成员属性，为了扩展方便可以直接使用Object类，也可以根据需求定义相应的接口</li>\n<li>在invoke方法中实现对委托对象的调用，根据需求对方法进行增强</li>\n<li>使用Proxy.newProxyInstance(…)方法创建代理对象，并提供要给获取代理对象的方法</li>\n</ol>\n<p><strong>代理类源码阅读</strong></p>\n<p>上文中基于jdk动态代理的代码实现中对于可*的产品经理来说已经完全满足了需求，但是对于具有Geek精神的程序员来说这远远不够，对于这种不知其所以然的东西往往让人感到不安。接下来我们将通过自定义的一个小工具类将动态生成的代理类保存到本地来一看究竟。</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 将生成的代理类保存为.class文件的工具类\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-08-15 0:27\n */</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ProxyUtils</span> </span>{\n    <span class=\"hljs-comment\">/**\n     * 将代理类保存到指定路径\n     *\n     * <span class=\"hljs-doctag\">@param</span> path           保存到的路径\n     * <span class=\"hljs-doctag\">@param</span> proxyClassName 代理类的Class名称\n     * <span class=\"hljs-doctag\">@param</span> interfaces     代理类接口\n     * <span class=\"hljs-doctag\">@return</span>\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">saveProxyClass</span><span class=\"hljs-params\">(String path, String proxyClassName, Class[] interfaces)</span></span>{\n        <span class=\"hljs-keyword\">if</span> (proxyClassName == <span class=\"hljs-keyword\">null</span> || path == <span class=\"hljs-keyword\">null</span>) {\n            <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">false</span>;\n        }\n        <span class=\"hljs-comment\">// 获取文件字节码，然后输出到目标文件中</span>\n        <span class=\"hljs-keyword\">byte</span>[] classFile = ProxyGenerator.generateProxyClass(proxyClassName, interfaces);\n        <span class=\"hljs-keyword\">try</span> (FileOutputStream out = <span class=\"hljs-keyword\">new</span> FileOutputStream(path)) {\n            out.write(classFile);\n            out.flush();\n        } <span class=\"hljs-keyword\">catch</span> (IOException e) {\n            e.printStackTrace();\n            <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">false</span>;\n        }\n        <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">true</span>;\n    }\n}\n</code></div></pre>\n<p><img src=\"C:/Users/mayn/Desktop/Export-fee80dfc-cc3a-4278-8434-28c90a3b9806/Untitled-726fc58f-198a-4f8d-9ed9-22f7a8d0e4e1.png\" alt=\"\" /></p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">// 此处是重点， 生成的代理类实现了IOrderService，并且继承了Proxy</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> $<span class=\"hljs-title\">Proxy0</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">Proxy</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">IOrderService</span> </span>{\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m1;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m3;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m2;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m0;\n\n    <span class=\"hljs-keyword\">public</span> $Proxy0(InvocationHandler var1) <span class=\"hljs-keyword\">throws</span>  {\n        <span class=\"hljs-keyword\">super</span>(var1);\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">equals</span><span class=\"hljs-params\">(Object var1)</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (Boolean)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m1, <span class=\"hljs-keyword\">new</span> Object[]{var1});\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var3) {\n            <span class=\"hljs-keyword\">throw</span> var3;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var4) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var4);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(Order var1)</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m3, <span class=\"hljs-keyword\">new</span> Object[]{var1});\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var3) {\n            <span class=\"hljs-keyword\">throw</span> var3;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var4) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var4);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> String <span class=\"hljs-title\">toString</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (String)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m2, (Object[])<span class=\"hljs-keyword\">null</span>);\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var2) {\n            <span class=\"hljs-keyword\">throw</span> var2;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var3);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">hashCode</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (Integer)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m0, (Object[])<span class=\"hljs-keyword\">null</span>);\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var2) {\n            <span class=\"hljs-keyword\">throw</span> var2;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var3);\n        }\n    }\n	\n    <span class=\"hljs-keyword\">static</span> {\n        <span class=\"hljs-keyword\">try</span> {\n		   <span class=\"hljs-comment\">// 通过反射获取Method对象</span>\n            m1 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"equals\"</span>, Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>));\n            m3 = Class.forName(<span class=\"hljs-string\">\"cn.mycookies.test08proxy.IOrderService\"</span>).getMethod(<span class=\"hljs-string\">\"saveOrder\"</span>, Class.forName(<span class=\"hljs-string\">\"cn.mycookies.test08proxy.Order\"</span>));\n            m2 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"toString\"</span>);\n            m0 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"hashCode\"</span>);\n        } <span class=\"hljs-keyword\">catch</span> (NoSuchMethodException var2) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> NoSuchMethodError(var2.getMessage());\n        } <span class=\"hljs-keyword\">catch</span> (ClassNotFoundException var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> NoClassDefFoundError(var3.getMessage());\n        }\n    }\n}\n</code></div></pre>\n<p>ps： 实习转正面试中被问到为什么jdk动态代理被代理的类为什么要实现接口？</p>\n<h2><a id=\"cglib_341\"></a>cglib动态代理</h2>\n<p>对于cglib我想大多数人应该都很陌生，或者是在学习Spring中AOP（面向切面编程）时听说了它使用jdk和cglib两种方式实现了动态代理。接下来笔者将针对cglib进行简要介绍。</p>\n<p>cglib动态代理和jdk动态代理类似，也是采用<strong>操作字节码机制</strong>，在运行时生成代理类。cglib 动态代理采取的是<strong>创建目标类的子类的方式</strong>，因为是子类化，我们可以达到近似使用被调用者本身的效果。</p>\n<p>字节码处理机制-指得是ASM来转换字节码并生成新的类</p>\n<p>注：spring中有完整的cglib相关的依赖，所以以下代码基于spring官方下载的demo中直接进行编写的</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 1. 订单服务-委托类，不需要再实现接口\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:42\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderService</span> </span>{\n    <span class=\"hljs-comment\">/**\n     * 保存订单接口\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">saveOrder</span><span class=\"hljs-params\">(String orderInfo)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">// 随机休眠，模拟订单保存需要的时间</span>\n        Thread.sleep(System.currentTimeMillis() &amp; <span class=\"hljs-number\">100</span>);\n        System.out.println(<span class=\"hljs-string\">\"订单：\"</span> + orderInfo + <span class=\"hljs-string\">\"  保存成功\"</span>);\n    }\n}\n\n<span class=\"hljs-comment\">/**\n * cglib动态代理工厂\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 18:36\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">ProxyFactory</span> <span class=\"hljs-keyword\">implements</span> <span class=\"hljs-title\">MethodInterceptor</span> </span>{\n\n    <span class=\"hljs-comment\">/**\n     * 委托对象， 即被代理对象\n      */</span>\n    <span class=\"hljs-keyword\">private</span> Object target;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-title\">ProxyFactory</span><span class=\"hljs-params\">(Object target)</span> </span>{\n        <span class=\"hljs-keyword\">this</span>.target = target;\n    }\n\n    <span class=\"hljs-comment\">/**\n     * 返回一个代理对象\n     * <span class=\"hljs-doctag\">@return</span>\n     */</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> Object <span class=\"hljs-title\">getProxyInstance</span><span class=\"hljs-params\">()</span></span>{\n        <span class=\"hljs-comment\">// 1. 创建一个工具类</span>\n        Enhancer enhancer = <span class=\"hljs-keyword\">new</span> Enhancer();\n        <span class=\"hljs-comment\">// 2. 设置父类</span>\n        enhancer.setSuperclass(target.getClass());\n        <span class=\"hljs-comment\">// 3. 设置回调函数</span>\n        enhancer.setCallback(<span class=\"hljs-keyword\">this</span>);\n        <span class=\"hljs-comment\">// 4.创建子类对象，即代理对象</span>\n        <span class=\"hljs-keyword\">return</span> enhancer.create();\n    }\n\n    <span class=\"hljs-meta\">@Override</span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> Object <span class=\"hljs-title\">intercept</span><span class=\"hljs-params\">(Object o, Method method, Object[] args, MethodProxy methodProxy)</span> <span class=\"hljs-keyword\">throws</span> Throwable </span>{\n        <span class=\"hljs-keyword\">long</span> start = System.currentTimeMillis();\n\n        Object result = method.invoke(target, args);\n\n        System.out.println(<span class=\"hljs-string\">\"cglib代理：保存订单用时: \"</span> + (System.currentTimeMillis() - start) + <span class=\"hljs-string\">\"ms\"</span>);\n        <span class=\"hljs-keyword\">return</span> result;\n    }\n}\n</code></div></pre>\n<p>​</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">/**\n * 使用cglib代理类来保存订单\n *\n * <span class=\"hljs-doctag\">@author</span> cruder\n * <span class=\"hljs-doctag\">@date</span> 2019-11-23 15:49\n **/</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">Client</span> </span>{\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">static</span> <span class=\"hljs-keyword\">void</span> <span class=\"hljs-title\">main</span><span class=\"hljs-params\">(String[] args)</span> <span class=\"hljs-keyword\">throws</span> InterruptedException </span>{\n        <span class=\"hljs-comment\">// 1. 创建委托对象</span>\n        OrderService orderService = <span class=\"hljs-keyword\">new</span> OrderService();\n        <span class=\"hljs-comment\">// 2. 获取代理对象</span>\n        OrderService orderServiceProxy = (OrderService) <span class=\"hljs-keyword\">new</span> ProxyFactory(orderService).getProxyInstance();\n        String saveFileName = <span class=\"hljs-string\">\"CglibOrderServiceDynamicProxy.class\"</span>;\n        ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), <span class=\"hljs-keyword\">new</span> Class[]{IOrderService.class});\n        orderServiceProxy.saveOrder(<span class=\"hljs-string\">\" cruder 新买的花裤衩 \"</span>);\n    }\n}\n</code></div></pre>\n<p><img src=\"http://source.mycookies.cn/201911232003_694.png?ERROR\" alt=\"\" /></p>\n<p>cglib动态代理实现步骤和jdk及其相似，可以分为以下几个步骤：</p>\n<ol>\n<li>创建一个实现MethodInterceptor接口的类</li>\n<li>在类中定义一个被代理对象的成员属性，为了扩展方便可以直接使用Object类，也可以根据需求定义相应的接口</li>\n<li>在invoke方法中实现对委托对象的调用，根据需求对方法进行增强</li>\n<li>使用Enhancer创建生成代理对象，并提供要给获取代理对象的方法</li>\n</ol>\n<p>cglib动态代理生成的代理类和jdk动态代理代码格式上几乎没有什么区别，唯一的区别在于cglib生成的代理类继承了仅仅Proxy类，而jdk动态代理生成的代理类继承了Proxy类的同时也实现了一个接口。代码如下：</p>\n<pre><div class=\"hljs\"><code class=\"lang-java\"><span class=\"hljs-comment\">// 生成一个Proxy的子类</span>\n<span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span> <span class=\"hljs-title\">OrderService</span> <span class=\"hljs-keyword\">extends</span> <span class=\"hljs-title\">Proxy</span> </span>{\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m1;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m2;\n    <span class=\"hljs-keyword\">private</span> <span class=\"hljs-keyword\">static</span> Method m0;\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-title\">OrderService</span><span class=\"hljs-params\">(InvocationHandler var1)</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">super</span>(var1);\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">boolean</span> <span class=\"hljs-title\">equals</span><span class=\"hljs-params\">(Object var1)</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (Boolean)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m1, <span class=\"hljs-keyword\">new</span> Object[]{var1});\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var3) {\n            <span class=\"hljs-keyword\">throw</span> var3;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var4) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var4);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> String <span class=\"hljs-title\">toString</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (String)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m2, (Object[])<span class=\"hljs-keyword\">null</span>);\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var2) {\n            <span class=\"hljs-keyword\">throw</span> var2;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var3);\n        }\n    }\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public</span> <span class=\"hljs-keyword\">final</span> <span class=\"hljs-keyword\">int</span> <span class=\"hljs-title\">hashCode</span><span class=\"hljs-params\">()</span> <span class=\"hljs-keyword\">throws</span>  </span>{\n        <span class=\"hljs-keyword\">try</span> {\n            <span class=\"hljs-keyword\">return</span> (Integer)<span class=\"hljs-keyword\">super</span>.h.invoke(<span class=\"hljs-keyword\">this</span>, m0, (Object[])<span class=\"hljs-keyword\">null</span>);\n        } <span class=\"hljs-keyword\">catch</span> (RuntimeException | Error var2) {\n            <span class=\"hljs-keyword\">throw</span> var2;\n        } <span class=\"hljs-keyword\">catch</span> (Throwable var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> UndeclaredThrowableException(var3);\n        }\n    }\n\n    <span class=\"hljs-keyword\">static</span> {\n        <span class=\"hljs-keyword\">try</span> {\n            m1 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"equals\"</span>, Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>));\n            m2 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"toString\"</span>);\n            m0 = Class.forName(<span class=\"hljs-string\">\"java.lang.Object\"</span>).getMethod(<span class=\"hljs-string\">\"hashCode\"</span>);\n        } <span class=\"hljs-keyword\">catch</span> (NoSuchMethodException var2) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> NoSuchMethodError(var2.getMessage());\n        } <span class=\"hljs-keyword\">catch</span> (ClassNotFoundException var3) {\n            <span class=\"hljs-keyword\">throw</span> <span class=\"hljs-keyword\">new</span> NoClassDefFoundError(var3.getMessage());\n        }\n    }\n}\n</code></div></pre>\n<h2><a id=\"jdk__VS__cglib_501\"></a>jdk动态代理  VS  cglib</h2>\n<p>JDK Proxy 的优势：</p>\n<ul>\n<li>最小化依赖关系，减少依赖意味着简化开发和维护，JDK 本身的支持，可能比 cglib 更加可靠。</li>\n<li>平滑进行 JDK 版本升级，而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。</li>\n<li>代码实现简单。</li>\n</ul>\n<p>cglib 优势：</p>\n<ul>\n<li>有的时候调用目标可能不便实现额外接口，从某种角度看，限定调用者实现接口是有些侵入性的实践，类似 cglib 动态代理就没有这种限制。</li>\n<li>只操作我们关心的类，而不必为其他相关类增加工作量。</li>\n</ul>\n<h2><a id=\"_514\"></a>总结</h2>\n<ol>\n<li>代理模式: 为其他对象提供一种代理以控制（隔离，使用接口）对这个对象的访问。</li>\n<li>jdk动态代理生成的代理类继承了Proxy类并实现了被代理的接口；而cglib生成的代理类则仅继承了Proxy类。</li>\n<li>jdk动态代理最大缺点：只能代理接口，既委托类必须实现相应的接口</li>\n<li>cglib缺点：由于是通过“子类化”的方式， 所以不能代理final的委托类或者普通委托类的final修饰的方法。</li>\n</ol>\n<h2><a id=\"QA_521\"></a>Q&amp;A</h2>\n<ol>\n<li>为什么jdk动态代理只能代理接口？</li>\n<li>Spring中AOP的实现采用那种代理方式？</li>\n<li>都说jdk动态代理性能远比cglib要差，如果是，依据是什么？</li>\n</ol>\n', '[{\"lev\":2,\"text\":\"为什么要使用代理模式\",\"id\":\"_8\"},{\"lev\":2,\"text\":\"什么是代理模式\",\"id\":\"_23\"},{\"lev\":2,\"text\":\"代理模式代码演进\",\"id\":\"_31\"},{\"lev\":3,\"text\":\"普通方式实现\",\"id\":\"_56\"},{\"lev\":3,\"text\":\"代理模式实现\",\"id\":\"_78\"},{\"lev\":3,\"text\":\"代理模式的优缺点\",\"id\":\"_146\"},{\"lev\":2,\"text\":\"Java三种代理模式的实现\",\"id\":\"Java_152\"},{\"lev\":2,\"text\":\"静态代理\",\"id\":\"_156\"},{\"lev\":2,\"text\":\"动态代理 - jdk\",\"id\":\"__jdk_160\"},{\"lev\":2,\"text\":\"cglib动态代理\",\"id\":\"cglib_341\"},{\"lev\":2,\"text\":\"jdk动态代理  VS  cglib\",\"id\":\"jdk__VS__cglib_501\"},{\"lev\":2,\"text\":\"总结\",\"id\":\"_514\"},{\"lev\":2,\"text\":\"Q&A\",\"id\":\"QA_521\"}]', 'Jann Lee', 0, 594, 21, 0, 1574510738810, 1574510738813, 1);
COMMIT;

-- ----------------------------
-- Table structure for blog_tag
-- ----------------------------
DROP TABLE IF EXISTS `blog_tag`;
CREATE TABLE `blog_tag` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '标签id',
  `tag_name` varchar(55) DEFAULT NULL COMMENT '标签名',
  `tag_desc` varchar(500) DEFAULT NULL COMMENT '标签简介',
  `tag_type` int(11) DEFAULT '1' COMMENT '表示的类型 1-标签 ，2-分类',
  `create_time` bigint(20) DEFAULT NULL COMMENT '创建时间',
  `update_time` bigint(20) DEFAULT NULL COMMENT '上一次修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='标签(标签，或者是分类标签)';

-- ----------------------------
-- Records of blog_tag
-- ----------------------------
BEGIN;
INSERT INTO `blog_tag` VALUES (2, '1231233', '21321312', 1, 1562599250426, 1570009466936);
INSERT INTO `blog_tag` VALUES (4, '测试', '分类', 1, 1562601934392, 1564995522442);
INSERT INTO `blog_tag` VALUES (5, '12321', 'dsfds ', 1, 1562601970490, 1562601970506);
INSERT INTO `blog_tag` VALUES (6, '设计模式', '关于23中设计模式', 2, 1563117423156, 1563117423161);
INSERT INTO `blog_tag` VALUES (7, '学习笔记', '学习总结', 1, 1563117468227, 1563117468236);
INSERT INTO `blog_tag` VALUES (14, '并发编程', '并发编程系列文章', 2, 1571760828365, 1571760828406);
INSERT INTO `blog_tag` VALUES (15, '并发', NULL, 1, 1571760850332, 1571760850339);
INSERT INTO `blog_tag` VALUES (16, '测试分类', NULL, 1, 1571763261049, 1571763261055);
COMMIT;

-- ----------------------------
-- Table structure for blog_tags
-- ----------------------------
DROP TABLE IF EXISTS `blog_tags`;
CREATE TABLE `blog_tags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `tag_id` int(11) NOT NULL,
  `blog_id` int(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `blogI_idx` (`blog_id`) USING BTREE,
  KEY `tagId_idx` (`tag_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=504 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='博客对应的标签';

-- ----------------------------
-- Records of blog_tags
-- ----------------------------
BEGIN;
INSERT INTO `blog_tags` VALUES (4, 2, 8);
INSERT INTO `blog_tags` VALUES (6, 2, 10);
INSERT INTO `blog_tags` VALUES (11, 2, 12);
INSERT INTO `blog_tags` VALUES (12, 4, 12);
INSERT INTO `blog_tags` VALUES (13, 5, 12);
INSERT INTO `blog_tags` VALUES (14, 2, 13);
INSERT INTO `blog_tags` VALUES (60, 4, 18);
INSERT INTO `blog_tags` VALUES (61, 4, 19);
INSERT INTO `blog_tags` VALUES (62, 4, 20);
INSERT INTO `blog_tags` VALUES (63, 4, 21);
INSERT INTO `blog_tags` VALUES (64, 4, 22);
INSERT INTO `blog_tags` VALUES (65, 4, 23);
INSERT INTO `blog_tags` VALUES (66, 4, 24);
INSERT INTO `blog_tags` VALUES (71, 2, 25);
INSERT INTO `blog_tags` VALUES (73, 5, 25);
INSERT INTO `blog_tags` VALUES (87, 2, 7);
INSERT INTO `blog_tags` VALUES (88, 5, 7);
INSERT INTO `blog_tags` VALUES (315, 2, 11);
INSERT INTO `blog_tags` VALUES (316, 4, 11);
INSERT INTO `blog_tags` VALUES (431, 2, 15);
INSERT INTO `blog_tags` VALUES (432, 4, 15);
INSERT INTO `blog_tags` VALUES (435, 2, 27);
INSERT INTO `blog_tags` VALUES (436, 2, 28);
INSERT INTO `blog_tags` VALUES (437, 2, 29);
INSERT INTO `blog_tags` VALUES (438, 2, 30);
INSERT INTO `blog_tags` VALUES (441, 2, 26);
INSERT INTO `blog_tags` VALUES (442, 5, 26);
INSERT INTO `blog_tags` VALUES (447, 4, 31);
INSERT INTO `blog_tags` VALUES (448, 4, 32);
INSERT INTO `blog_tags` VALUES (449, 2, 33);
INSERT INTO `blog_tags` VALUES (450, 4, 33);
INSERT INTO `blog_tags` VALUES (451, 2, 9);
INSERT INTO `blog_tags` VALUES (459, 2, 34);
INSERT INTO `blog_tags` VALUES (460, 4, 44);
INSERT INTO `blog_tags` VALUES (461, 4, 45);
INSERT INTO `blog_tags` VALUES (466, 4, 42);
INSERT INTO `blog_tags` VALUES (467, 4, 46);
INSERT INTO `blog_tags` VALUES (468, 4, 47);
INSERT INTO `blog_tags` VALUES (470, 7, 16);
INSERT INTO `blog_tags` VALUES (471, 7, 17);
INSERT INTO `blog_tags` VALUES (473, 2, 14);
INSERT INTO `blog_tags` VALUES (481, 4, 49);
INSERT INTO `blog_tags` VALUES (482, 4, 48);
INSERT INTO `blog_tags` VALUES (483, 5, 50);
INSERT INTO `blog_tags` VALUES (484, 7, 51);
INSERT INTO `blog_tags` VALUES (485, 15, 51);
INSERT INTO `blog_tags` VALUES (490, 7, 52);
INSERT INTO `blog_tags` VALUES (491, 15, 52);
INSERT INTO `blog_tags` VALUES (494, 7, 53);
INSERT INTO `blog_tags` VALUES (495, 15, 53);
INSERT INTO `blog_tags` VALUES (496, 7, 54);
INSERT INTO `blog_tags` VALUES (497, 15, 54);
INSERT INTO `blog_tags` VALUES (498, 15, 55);
INSERT INTO `blog_tags` VALUES (499, 15, 56);
INSERT INTO `blog_tags` VALUES (501, 15, 57);
INSERT INTO `blog_tags` VALUES (502, 7, 58);
INSERT INTO `blog_tags` VALUES (503, 7, 59);
COMMIT;

-- ----------------------------
-- Table structure for comment_info
-- ----------------------------
DROP TABLE IF EXISTS `comment_info`;
CREATE TABLE `comment_info` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '评论主键id',
  `pid` int(11) DEFAULT '0' COMMENT '父评论id',
  `target_id` int(11) NOT NULL COMMENT '被评论元素得id',
  `target_type` int(11) NOT NULL DEFAULT '0' COMMENT '目标类型，对博客，相册，留言板等',
  `user_email` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '用户邮箱',
  `user_name` varchar(50) COLLATE utf8_bin DEFAULT '' COMMENT '用户名，冗余字段',
  `user_icon` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT '用户图标',
  `content` varchar(1000) COLLATE utf8_bin DEFAULT NULL COMMENT '评论的内容',
  `like_count` int(10) DEFAULT '0' COMMENT '点赞数',
  `create_time` bigint(20) DEFAULT NULL,
  `update_time` bigint(20) DEFAULT NULL,
  `comment_status` int(11) DEFAULT '1' COMMENT '评论的状态，0-无效，1-有效',
  PRIMARY KEY (`id`),
  KEY `IDX_TARGET_TYPE_ID` (`target_type`,`target_id`)
) ENGINE=InnoDB AUTO_INCREMENT=159 DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT COMMENT='评论表';

-- ----------------------------
-- Records of comment_info
-- ----------------------------
BEGIN;
INSERT INTO `comment_info` VALUES (59, 0, 16, 1, 'liangfhhd@163.com', '雪山飞狐', NULL, '博客不错，学习一下~', 5, 1565594056132, 1633595949903, 1);
INSERT INTO `comment_info` VALUES (60, 0, 0, 3, 'liqiang1chn@163.com', '测试', NULL, '哈哈哈', 7, 1566055826182, 1569664404016, 1);
INSERT INTO `comment_info` VALUES (61, 0, 14, 1, '1250188121@qq.com', 'MrSli', NULL, '加油', 0, 1566267363793, NULL, 1);
INSERT INTO `comment_info` VALUES (62, 0, 16, 1, '1152735410@qq.com', '自由战士', NULL, '学习一下', 4, 1567054282223, 1577616860668, 1);
INSERT INTO `comment_info` VALUES (63, 0, 0, 3, 'admin@qq.com', 'ppx', NULL, 'too many bug!!!', 2, 1567334329817, 1569664408183, 1);
INSERT INTO `comment_info` VALUES (64, 0, 0, 3, 'admin@qq.com', 'ppx', NULL, 'too many bug!!!', 2, 1567334332068, 1569664408704, 1);
INSERT INTO `comment_info` VALUES (65, 0, 0, 3, 'admin@qq.com', 'ppx', NULL, 'too many bug!!!', 3, 1567334336569, 1586190312866, 1);
INSERT INTO `comment_info` VALUES (66, 0, 0, 3, 'admin@qq.com', 'ppx', NULL, 'too many bug!!!', 5, 1567334337939, 1569674239344, 1);
INSERT INTO `comment_info` VALUES (67, 0, 0, 3, 'admin@qq.com', 'ppx', NULL, 'too many bug!!!', 2, 1567334339749, 1569664411159, 1);
INSERT INTO `comment_info` VALUES (68, 0, 17, 1, 'xph11123@163.com', 'Xph', NULL, '望闻问切', 0, 1568702753678, NULL, 1);
INSERT INTO `comment_info` VALUES (69, 0, 0, 3, 'admin@qq.com', 'siop', NULL, '        ', 3, 1569142617873, 1569664411879, 1);
INSERT INTO `comment_info` VALUES (70, 0, 0, 3, 'admin@qq.com', 'siop', NULL, '留言记得过滤空格', 4, 1569142642032, 1569664412463, 1);
INSERT INTO `comment_info` VALUES (71, 0, 0, 3, 'meiyou@hehe.com', '我最帅', NULL, '支持', 2, 1569674324619, 1620723241406, 1);
INSERT INTO `comment_info` VALUES (72, 0, 0, 3, 'meiyou@hehe.com', '我最帅', NULL, '支持', 0, 1569674325335, NULL, 1);
INSERT INTO `comment_info` VALUES (73, 0, 0, 3, 'meiyou@hehe.com', '我最帅', NULL, '支持', 1, 1569674329062, 1569674343046, 1);
INSERT INTO `comment_info` VALUES (74, 0, 0, 3, 'meiyou@hehe.com', '我最帅', NULL, '支持', 3, 1569674330633, 1572167644962, 1);
INSERT INTO `comment_info` VALUES (75, 0, 0, 3, 'meiyou@hehe.com', '我最帅,我最牛逼啊', NULL, '哈哈，嘿嘿，呵呵', 4, 1569674385477, 1571544738400, 1);
INSERT INTO `comment_info` VALUES (76, 0, 0, 3, '33@22.com', '333', NULL, '测试数据删了吧', 1, 1571796863504, 1572399247824, 1);
INSERT INTO `comment_info` VALUES (77, 0, 0, 3, '33@22.com', '333', NULL, '测试数据删了吧', 4, 1571796875438, 1574284045381, 1);
INSERT INTO `comment_info` VALUES (78, 0, 0, 3, '33@22.com', '333', NULL, '测试数据删了吧', 3, 1571796875783, 1573721211275, 1);
INSERT INTO `comment_info` VALUES (79, 0, 0, 3, '33@22.com', '333', NULL, '测试数据删了吧', 5, 1571796875920, 1573721210409, 1);
INSERT INTO `comment_info` VALUES (80, 0, 0, 3, '33@22.com', '333', NULL, '测试数据删了吧', 5, 1571796893242, 1573721207289, 1);
INSERT INTO `comment_info` VALUES (81, 0, 53, 1, '3361736061@qq.com', '寤寐·悟', NULL, '如果这样的话，程序运行的时候会不会很重？', 1, 1572266293932, 1623218611454, 1);
INSERT INTO `comment_info` VALUES (82, 0, 52, 1, '2344@wqe.com', '34', NULL, '234', 1, 1572269050489, 1591793516072, 1);
INSERT INTO `comment_info` VALUES (83, 0, 0, 3, '875742843@qq.com', '123', NULL, '驱蚊器翁', 4, 1572418984093, 1573787523212, 1);
INSERT INTO `comment_info` VALUES (84, 0, 53, 1, 'liqiang@weli.cn', '管理员', NULL, '@ 寤寐·悟\n你所说的重，的意思是synchronized是比较重量级的锁，可能会存在性能问题吧？ 其实在Java6.0开始对synchronized关键字进行了优化，性能已经不再是问题了。\n这里的问题是在get方法中没有使用synchronized关键字进行同步，不能保证线程间的可见性，也就是说当一个线程执行完addOne方法时，get方法看到的结果可能没有变化（从线程本地内存中读取而非主内存）。', 0, 1572452419085, NULL, 1);
INSERT INTO `comment_info` VALUES (85, 0, 17, 1, 'admin@163.com', 'admin', NULL, 'hahaha', 0, 1572572364691, NULL, 1);
INSERT INTO `comment_info` VALUES (86, 0, 16, 1, 'df@a.com', 'sdf', NULL, 'dfsdf', 1, 1573122080271, 1573122088242, 1);
INSERT INTO `comment_info` VALUES (87, 0, 57, 1, '562547535@qq.com', '史迪仔', NULL, '能不能跟大神学一下 怎么搭个人博客', 10, 1574433659162, 1625224887281, 1);
INSERT INTO `comment_info` VALUES (88, 0, 0, 3, 'henanchendi@163.com', 'rubik', NULL, '您好，看了您做的博客感觉很厉害。我刚开始学习java，请问您可以针对这个个人博客的搭建出一版教程吗？', 2, 1575168576666, 1630479865459, 1);
INSERT INTO `comment_info` VALUES (89, 0, 0, 3, 'liqiang1chn@163.com', '管理员', NULL, '@rubik 关注公众号 cruder ，后台留言，我们私下沟通', 1, 1575382884752, 1578930037040, 1);
INSERT INTO `comment_info` VALUES (90, 0, 0, 3, '791825151@qq.com', 'Goodboy小海', NULL, '支持强哥 哈哈哈', 0, 1576076784213, NULL, 1);
INSERT INTO `comment_info` VALUES (91, 0, 0, 3, '791825151@qq.com', 'Goodboy小海', NULL, '支持强哥 哈哈哈', 0, 1576076795153, NULL, 1);
INSERT INTO `comment_info` VALUES (92, 0, 0, 3, '791825151@qq.com', 'Goodboy小海', NULL, '支持强哥 哈哈哈', 1, 1576076802011, 1595127623402, 1);
INSERT INTO `comment_info` VALUES (93, 0, 0, 3, '791825151@qq.com', 'Goodboy小海', NULL, '支持强哥 哈哈哈', 1, 1576076804459, 1596592551743, 1);
INSERT INTO `comment_info` VALUES (94, 0, 0, 3, 'asdsa@qq.com', 'asda', NULL, 'sdas', 0, 1576322539138, NULL, 1);
INSERT INTO `comment_info` VALUES (95, 0, 0, 3, 'asdsa@qq.com', 'asda', NULL, 'sdas', 0, 1576322539380, NULL, 1);
INSERT INTO `comment_info` VALUES (96, 0, 0, 3, '791825151@qq.com', 'Goodboy小海', NULL, '最近怎么没有更新', 0, 1577153527371, NULL, 1);
INSERT INTO `comment_info` VALUES (97, 0, 0, 3, 'qq@qq.com', '二分法', NULL, '突然同意', 1, 1577207917741, 1580541166339, 1);
INSERT INTO `comment_info` VALUES (98, 0, 0, 3, '15085348329@qq.con', 'dddd', NULL, 'fsdfsz', 3, 1577256834141, 1623505983422, 1);
INSERT INTO `comment_info` VALUES (99, 0, 0, 3, '2657251370@qq.com', '去外地玩v', NULL, '阿所产生的', 0, 1580541230042, NULL, 1);
INSERT INTO `comment_info` VALUES (100, 0, 0, 3, '2657251370@qq.com', '去外地玩v', NULL, '阿所产生的', 0, 1580541232508, NULL, 1);
INSERT INTO `comment_info` VALUES (101, 0, 0, 3, '2657251370@qq.com', '去外地玩v', NULL, '阿所产生的', 2, 1580541232897, 1619712382589, 1);
INSERT INTO `comment_info` VALUES (102, 0, 0, 3, '2657251370@qq.com', '去外地玩v', NULL, '阿所产生的', 3, 1580541233115, 1586190339486, 1);
INSERT INTO `comment_info` VALUES (103, 0, 0, 3, '2657251370@qq.com', '去外地玩v', NULL, '阿所产生的', 5, 1580541233338, 1623505991080, 1);
INSERT INTO `comment_info` VALUES (104, 0, 57, 1, '1011267132@qq.com', 'hhh123', NULL, '简洁，直观，写得很好', 9, 1581836288674, 1633771937098, 1);
INSERT INTO `comment_info` VALUES (105, 0, 0, 3, 'asda123@qq.com', 'qu 外地玩了吗', NULL, '最近怎么没有更新', 3, 1582387050884, 1586190321746, 1);
INSERT INTO `comment_info` VALUES (106, 0, 17, 1, '11@qq.com', '35', NULL, '35153', 2, 1582446012722, 1589778245538, 1);
INSERT INTO `comment_info` VALUES (107, 0, 0, 3, 'liqiang@weli.cn', '管理员', NULL, '各位老铁，最近工作忙，博客没有时间维护，以后有时间还会继续搞起来的！感兴趣的可以先关注我的微信公众号 “Java填坑笔记”，最近博客都发布在公众号上了 ', 4, 1582881634133, 1597728589764, 1);
INSERT INTO `comment_info` VALUES (108, 0, 0, 3, '1712082610@qq.com', '小白', NULL, '大佬这个是用maven 几点几呀', 6, 1583685101398, 1600307600036, 1);
INSERT INTO `comment_info` VALUES (109, 0, 0, 3, '1011267132@qq.com', 'hhh123', NULL, '没更新吗', 1, 1586190532283, 1594195638043, 1);
INSERT INTO `comment_info` VALUES (110, 0, 0, 3, '1011267132@qq.com', 'hhh123', NULL, '没更新吗', 3, 1586190555933, 1622984534868, 1);
INSERT INTO `comment_info` VALUES (111, 0, 0, 3, '1011267132@qq.com', 'ppx', NULL, '留言功能还是有一些bug，点提交留言，看不到自己发的留言，就容易多次提交', 6, 1586190727612, 1597728586921, 1);
INSERT INTO `comment_info` VALUES (112, 0, 0, 3, '1011267132@qq.com', 'ppx', NULL, '留言功能还是有一些bug，点提交留言，看不到自己发的留言，就容易多次提交', 18, 1586190734336, 1623506024886, 1);
INSERT INTO `comment_info` VALUES (113, 0, 17, 1, 'aodi@123', '12', NULL, '111', 0, 1592486129849, NULL, 1);
INSERT INTO `comment_info` VALUES (114, 0, 17, 1, 'aodi@123', '12', NULL, '111', 1, 1592486133128, 1632105078910, 1);
INSERT INTO `comment_info` VALUES (115, 0, 17, 1, 'aodi@123', '12', NULL, '111', 1, 1592486134968, 1619691421871, 1);
INSERT INTO `comment_info` VALUES (116, 0, 17, 1, 'aodi@123', '12', NULL, '111', 1, 1592486136464, 1619691414442, 1);
INSERT INTO `comment_info` VALUES (117, 0, 0, 3, '111@qq.com', '111', NULL, 'qqq', 7, 1593578921528, 1622984532520, 1);
INSERT INTO `comment_info` VALUES (118, 0, 59, 1, 'p@qq.com', '佚名', NULL, '因为多态', 3, 1605948397893, 1631845093097, 1);
INSERT INTO `comment_info` VALUES (119, 0, 0, 3, '12134566@qq.com', 'hahha', NULL, 'hahahh', 2, 1606130923052, 1623506018951, 1);
INSERT INTO `comment_info` VALUES (120, 0, 0, 3, '12134566@qq.com', 'hahha', NULL, 'hahahh', 4, 1606130927589, 1623506015215, 1);
INSERT INTO `comment_info` VALUES (121, 0, 0, 3, '12134566@qq.com', 'hahha', NULL, 'hahahh', 9, 1606130927781, 1623506002865, 1);
INSERT INTO `comment_info` VALUES (122, 0, 0, 3, '12134566@qq.com', 'hahha', NULL, 'hahahh', 13, 1606130928005, 1626254194926, 1);
INSERT INTO `comment_info` VALUES (123, 0, 0, 3, '12134566@qq.com', 'hahha', NULL, 'hahahh', 25, 1606130928183, 1630911919579, 1);
INSERT INTO `comment_info` VALUES (124, 0, 57, 1, 'dsds@qq.com', 'sdsd', NULL, '像大显身手', 4, 1618227181309, 1633771934667, 1);
INSERT INTO `comment_info` VALUES (125, 0, 16, 1, '173704611@qq.com', 'sluna', NULL, '真棒儿', 0, 1618231954788, NULL, 1);
INSERT INTO `comment_info` VALUES (126, 0, 0, 3, '2855986310@qq.com', 'orochi', NULL, 'oTL', 24, 1620723287300, 1624946688226, 1);
INSERT INTO `comment_info` VALUES (127, 0, 59, 1, '2855986310@qq.com', '3750', NULL, 'yyds', 3, 1620890400733, 1634794866424, 1);
INSERT INTO `comment_info` VALUES (128, 0, 59, 1, 'qwqwqwqw@qq.com', '3750', NULL, 'yyds', 4, 1620890417612, 1631883437414, 1);
INSERT INTO `comment_info` VALUES (129, 0, 16, 1, '123@qq.com', '完全解决', NULL, '我去问问无无无无无无无无无无无无无无无无无无', 1, 1621567484461, 1621567491185, 1);
INSERT INTO `comment_info` VALUES (130, 0, 16, 1, '123@qq.com', '完全解决', NULL, '我去问问无无无无无无无无无无无无无无无无无无', 0, 1621567487876, NULL, 1);
INSERT INTO `comment_info` VALUES (131, 0, 17, 1, '1231@qq.com', '++', NULL, '+++++', 0, 1622978731150, NULL, 1);
INSERT INTO `comment_info` VALUES (132, 0, 17, 1, '1233332321@qq.com', '++', NULL, '+++++', 0, 1622978740867, NULL, 1);
INSERT INTO `comment_info` VALUES (133, 0, 17, 1, '1233332321@qq.com', '++', NULL, '+++++', 3, 1622978742940, 1634087227205, 1);
INSERT INTO `comment_info` VALUES (134, 0, 57, 1, '166@163.com', 'ffs', NULL, '参观大佬', 2, 1623210420406, 1633771924647, 1);
INSERT INTO `comment_info` VALUES (135, 0, 16, 1, '166@163.com', '11', NULL, '11', 1, 1623210523018, 1635588996349, 1);
INSERT INTO `comment_info` VALUES (136, 0, 0, 3, 'aaaaa@qq.com', 'aaa', NULL, 'aaaaaaa', 4, 1623844785031, 1624852641319, 1);
INSERT INTO `comment_info` VALUES (137, 0, 57, 1, '11@qq.com', '1', NULL, '1111', 0, 1623854991790, NULL, 1);
INSERT INTO `comment_info` VALUES (138, 0, 57, 1, '11@qq.com', '1', NULL, '1111', 1, 1623855001058, 1629257761393, 1);
INSERT INTO `comment_info` VALUES (139, 0, 57, 1, 'shui2457@163.com', 'java', NULL, 'rerewrer', 1, 1623895358569, 1625244370585, 1);
INSERT INTO `comment_info` VALUES (140, 0, 55, 1, '12321@163.com', '12313', NULL, '1231231231233', 0, 1624603637488, NULL, 1);
INSERT INTO `comment_info` VALUES (141, 0, 55, 1, '12321@163.com', '12313', NULL, '1231231231233112312312331322222222222222', 1, 1624603644008, 1630666701574, 1);
INSERT INTO `comment_info` VALUES (142, 0, 0, 3, '53354351@qq.com', '‘’‘’', NULL, '？；', 0, 1625465899161, NULL, 1);
INSERT INTO `comment_info` VALUES (143, 0, 0, 3, '53354351@qq.com', '‘’‘’', NULL, '？；', 5, 1625465900674, 1632555670021, 1);
INSERT INTO `comment_info` VALUES (144, 0, 0, 3, '28378886@qq.com', '微笑周', NULL, '来了就要试试', 12, 1625536008666, 1632555667727, 1);
INSERT INTO `comment_info` VALUES (145, 0, 0, 3, '28378886@qq.com', '微笑周', NULL, '来了就要试试', 20, 1625536012313, 1632555666599, 1);
INSERT INTO `comment_info` VALUES (146, 0, 57, 1, 'wzb116@126.com', 'laowan', NULL, '不错', 2, 1626934934732, 1627634127680, 1);
INSERT INTO `comment_info` VALUES (147, 0, 57, 1, 'test@test.com', 'aa', NULL, 'hello', 4, 1628489375303, 1634708407492, 1);
INSERT INTO `comment_info` VALUES (148, 0, 0, 3, '1181378991@qq.com', 'sckd', NULL, '请问这个博客源码有吗，你在码云的代码不是这个博客的啊', 13, 1629096412279, 1634705160618, 1);
INSERT INTO `comment_info` VALUES (149, 0, 0, 3, '1181378991@qq.com', 'sckd', NULL, '请问这个博客源码有吗，你在码云的代码不是这个博客的啊', 17, 1629096415172, 1634708416352, 1);
INSERT INTO `comment_info` VALUES (150, 0, 55, 1, 'aaa@qq.com', 'aaa', NULL, ';l;l;;kjkjkjk', 2, 1630666757276, 1634658333630, 1);
INSERT INTO `comment_info` VALUES (151, 0, 0, 3, 'ddddd@666.com', 'xx', NULL, 'xxxx', 15, 1631198753906, 1634713071642, 1);
INSERT INTO `comment_info` VALUES (152, 0, 52, 1, '2@qq.com', '11', NULL, '222', 2, 1634287644349, 1634307200362, 1);
INSERT INTO `comment_info` VALUES (153, 0, 0, 3, '645154@qq.com', '11', NULL, '1111', 1, 1634708444897, 1634713074411, 1);
INSERT INTO `comment_info` VALUES (154, 0, 0, 3, '645154@qq.com', '11', NULL, '1111', 1, 1634708445260, 1634713075394, 1);
INSERT INTO `comment_info` VALUES (155, 0, 0, 3, '645154@qq.com', '11', NULL, '1111', 1, 1634708448799, 1634713076307, 1);
INSERT INTO `comment_info` VALUES (156, 0, 0, 3, '645154@qq.com', '11', NULL, '1111', 3, 1634708449218, 1635339111837, 1);
INSERT INTO `comment_info` VALUES (157, 0, 0, 3, '645154@qq.com', '11', NULL, '1111', 4, 1634708449368, 1635339110677, 1);
INSERT INTO `comment_info` VALUES (158, 0, 0, 3, '2191377759@qq.com', 'wuxin', NULL, '测试一下', 1, 1635339141523, 1635339226976, 1);
COMMIT;

-- ----------------------------
-- Table structure for photo
-- ----------------------------
DROP TABLE IF EXISTS `photo`;
CREATE TABLE `photo` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `album_id` smallint(6) NOT NULL,
  `img_url` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `alt` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_email` varchar(50) COLLATE utf8_bin DEFAULT NULL,
  `user_name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `create_time` bigint(20) DEFAULT NULL,
  `update_time` bigint(20) DEFAULT NULL,
  `user_status` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  KEY `INX_VISITOR_EMAIL_NAME` (`user_email`,`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;

-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (2, 'liqiang@chn.com', 'liqiang', 1562350937419, NULL, 1);
INSERT INTO `user` VALUES (3, 'zhangsan@qq.com', '张三', 1562351743861, NULL, 1);
INSERT INTO `user` VALUES (4, 'liqiang@163.com', '123123', 1562425148332, 1564049079348, 1);
INSERT INTO `user` VALUES (5, 'liqiang@weli.cn', '管理员', 1562425188988, 1572452419084, 1);
INSERT INTO `user` VALUES (6, 'liqiang1chn@163.com', '管理员', 1562833110734, 1575382884637, 1);
INSERT INTO `user` VALUES (7, '9999@qq.com', '李强', 1563368767000, NULL, 1);
INSERT INTO `user` VALUES (8, '9999@qq.com', '李强', 1563368767000, NULL, 1);
INSERT INTO `user` VALUES (9, '944184544@qq.com', '李强', 1563368783873, NULL, 1);
INSERT INTO `user` VALUES (10, 'shenlanluck@gmail.com', 'shenlan', 1563872238477, NULL, 1);
INSERT INTO `user` VALUES (11, '111111@11', '1', 1564739112128, NULL, 1);
INSERT INTO `user` VALUES (12, 'liangfhhd@163.com', '雪山飞狐', 1565594055903, NULL, 1);
INSERT INTO `user` VALUES (13, '1250188121@qq.com', 'MrSli', 1566267363738, NULL, 1);
INSERT INTO `user` VALUES (14, '1152735410@qq.com', '自由战士', 1567054282101, NULL, 1);
INSERT INTO `user` VALUES (15, 'admin@qq.com', 'siop', 1567334329640, 1569142617808, 1);
INSERT INTO `user` VALUES (16, 'xph11123@163.com', 'Xph', 1568702753670, NULL, 1);
INSERT INTO `user` VALUES (18, 'meiyou@hehe.com', '我最帅,我最牛逼啊', 1569674324547, 1569674385475, 1);
INSERT INTO `user` VALUES (19, '33@22.com', '333', 1571796863498, NULL, 1);
INSERT INTO `user` VALUES (20, '3361736061@qq.com', '寤寐·悟', 1572266293931, NULL, 1);
INSERT INTO `user` VALUES (21, '2344@wqe.com', '34', 1572269050489, NULL, 1);
INSERT INTO `user` VALUES (22, '875742843@qq.com', '123', 1572418984092, NULL, 1);
INSERT INTO `user` VALUES (23, 'admin@163.com', 'admin', 1572572364690, NULL, 1);
INSERT INTO `user` VALUES (24, 'df@a.com', 'sdf', 1573122080270, NULL, 1);
INSERT INTO `user` VALUES (25, '562547535@qq.com', '史迪仔', 1574433659123, NULL, 1);
INSERT INTO `user` VALUES (26, 'henanchendi@163.com', 'rubik', 1575168576665, NULL, 1);
INSERT INTO `user` VALUES (27, '791825151@qq.com', 'Goodboy小海', 1576076784212, NULL, 1);
INSERT INTO `user` VALUES (28, 'asdsa@qq.com', 'asda', 1576322539137, NULL, 1);
INSERT INTO `user` VALUES (29, 'qq@qq.com', '二分法', 1577207917740, NULL, 1);
INSERT INTO `user` VALUES (30, '15085348329@qq.con', 'dddd', 1577256834140, NULL, 1);
INSERT INTO `user` VALUES (31, '2657251370@qq.com', '去外地玩v', 1580541230041, NULL, 1);
INSERT INTO `user` VALUES (32, '1011267132@qq.com', 'ppx', 1581836288673, 1586190727612, 1);
INSERT INTO `user` VALUES (33, 'asda123@qq.com', 'qu 外地玩了吗', 1582387050883, NULL, 1);
INSERT INTO `user` VALUES (34, '11@qq.com', '1', 1582446012721, 1623854991789, 1);
INSERT INTO `user` VALUES (35, '1712082610@qq.com', '小白', 1583685101397, NULL, 1);
INSERT INTO `user` VALUES (36, 'aodi@123', '12', 1592486129848, NULL, 1);
INSERT INTO `user` VALUES (37, '111@qq.com', '111', 1593578921527, NULL, 1);
INSERT INTO `user` VALUES (38, 'p@qq.com', '佚名', 1605948397862, NULL, 1);
INSERT INTO `user` VALUES (39, '12134566@qq.com', 'hahha', 1606130923051, NULL, 1);
INSERT INTO `user` VALUES (40, 'dsds@qq.com', 'sdsd', 1618227181304, NULL, 1);
INSERT INTO `user` VALUES (41, '173704611@qq.com', 'sluna', 1618231954787, NULL, 1);
INSERT INTO `user` VALUES (42, '2855986310@qq.com', '3750', 1620723287296, 1620890400731, 1);
INSERT INTO `user` VALUES (43, 'qwqwqwqw@qq.com', '3750', 1620890417612, NULL, 1);
INSERT INTO `user` VALUES (44, '123@qq.com', '完全解决', 1621567484458, NULL, 1);
INSERT INTO `user` VALUES (45, '1231@qq.com', '++', 1622978731149, NULL, 1);
INSERT INTO `user` VALUES (46, '1233332321@qq.com', '++', 1622978740866, NULL, 1);
INSERT INTO `user` VALUES (47, '166@163.com', '11', 1623210420406, 1623210523018, 1);
INSERT INTO `user` VALUES (48, 'aaaaa@qq.com', 'aaa', 1623844785030, NULL, 1);
INSERT INTO `user` VALUES (49, 'shui2457@163.com', 'java', 1623895358568, NULL, 1);
INSERT INTO `user` VALUES (50, '12321@163.com', '12313', 1624603637488, NULL, 1);
INSERT INTO `user` VALUES (51, '53354351@qq.com', '‘’‘’', 1625465899145, NULL, 1);
INSERT INTO `user` VALUES (52, '28378886@qq.com', '微笑周', 1625536008665, NULL, 1);
INSERT INTO `user` VALUES (53, 'wzb116@126.com', 'laowan', 1626934934731, NULL, 1);
INSERT INTO `user` VALUES (54, 'test@test.com', 'aa', 1628489375302, NULL, 1);
INSERT INTO `user` VALUES (55, '1181378991@qq.com', 'sckd', 1629096412277, NULL, 1);
INSERT INTO `user` VALUES (56, 'aaa@qq.com', 'aaa', 1630666757276, NULL, 1);
INSERT INTO `user` VALUES (57, 'ddddd@666.com', 'xx', 1631198753883, NULL, 1);
INSERT INTO `user` VALUES (58, '2@qq.com', '11', 1634287644348, NULL, 1);
INSERT INTO `user` VALUES (59, '645154@qq.com', '11', 1634708444897, NULL, 1);
INSERT INTO `user` VALUES (60, '2191377759@qq.com', 'wuxin', 1635339141522, NULL, 1);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
